5180 fichiers 38.5 Mo 0 téléchargements 249 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / hooks / inventory / useInventoryBadges.ts
useInventoryBadges.ts 8.2 Ko Télécharger le fichier
import { BadgeReceivedEvent, BadgesEvent, RequestBadgesComposer, SetActivatedBadgesComposer } from '@octane/renderer';
import { useEffect, useRef, useState } from 'react';
import { registerSharedHook, useSharedHook } from '@/state/useSharedHook';
import { GetConfigurationValue, SendMessageComposer, UnseenItemCategory } from '../../api';
import { rememberBadgeRarityFromPacket, rememberReceivedBadgeRarity } from '../../api/badges/badgeRarity';
import { useMessageEvent } from '../events';
import { createTrailingSender } from './badgeWearSender';
import { useSharedVisibility } from '../useSharedVisibility';
import { useInventoryUnseenTracker } from './useInventoryUnseenTracker';

const useInventoryBadgesState = () => {
    const [needsUpdate, setNeedsUpdate] = useState(true);
    const [badgeCodes, setBadgeCodes] = useState<string[]>([]);
    const [badgeIds, setBadgeIds] = useState<Map<string, number>>(new Map<string, number>());
    const [activeBadgeCodes, setActiveBadgeCodes] = useState<(string | null)[]>([]);
    const activeBadgeCodesRef = useRef<(string | null)[]>([]);
    activeBadgeCodesRef.current = activeBadgeCodes;
    const [selectedBadgeCode, setSelectedBadgeCode] = useState<string>(null);
    const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility();
    const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker();

    const maxBadgeCount = GetConfigurationValue<number>('user.badges.max.slots', 5);
    const isWearingBadge = (badgeCode: string) => activeBadgeCodes.some((code) => code === badgeCode);
    const canWearBadges = () => activeBadgeCodes.filter(Boolean).length < maxBadgeCount;

    const toFixedSlots = (arr: (string | null)[]): (string | null)[] => {
        const seen = new Set<string>();
        return Array.from({ length: maxBadgeCount }, (_, i) => {
            const code = arr[i] || null;
            if (!code || seen.has(code)) return null;
            seen.add(code);
            return code;
        });
    };

    const sendActiveBadges = (badges: (string | null)[]) => {
        const composer = new SetActivatedBadgesComposer();
        for (let i = 0; i < maxBadgeCount; i++) composer.addActivatedBadge(badges[i] ?? '');
        SendMessageComposer(composer);
    };

    const sendActiveBadgesRef = useRef(sendActiveBadges);
    sendActiveBadgesRef.current = sendActiveBadges;
    const [wearSender] = useState(() => createTrailingSender<(string | null)[]>((slots) => sendActiveBadgesRef.current(slots)));

    // A change still waiting is sent when the hook goes away, not dropped.
    useEffect(() => () => wearSender.flush(), [wearSender]);

    /**
     * Keeps a new set of worn badges at once and sends the last of a quick run of changes. Every change is worked out
     * from a ref, not inside a state updater: an updater may run twice (StrictMode) and sent the set twice.
     */
    const wearBadges = (slots: (string | null)[]) => {
        activeBadgeCodesRef.current = slots;
        setActiveBadgeCodes(slots);
        wearSender.push(slots);
    };

    const toggleBadge = (badgeCode: string) => {
        const slots = toFixedSlots(activeBadgeCodesRef.current);
        const index = slots.indexOf(badgeCode);

        if (index === -1) {
            const emptySlot = slots.indexOf(null);
            if (emptySlot === -1) return;

            slots[emptySlot] = badgeCode;
        } else {
            slots[index] = null;
        }

        wearBadges(slots);
    };

    const getBadgeId = (badgeCode: string) => {
        const index = badgeCodes.indexOf(badgeCode);

        if (index === -1) return 0;

        return badgeIds.get(badgeCode) ?? 0;
    };

    useMessageEvent<BadgesEvent>(BadgesEvent, (event) => {
        const parser = event.getParser();
        const allBadgeCodes = parser.getAllBadgeCodes();

        // Official `BadgesModel.initBadges`: the packet itself carries the owner count and rarity
        // tier of every badge, so the grid never has to ask the leaderboard for them.
        rememberBadgeRarityFromPacket(parser.getBadgeDetails());

        setBadgeIds(() => {
            const newValue = new Map<string, number>();

            allBadgeCodes.forEach((code) => {
                const badgeId = parser.getBadgeId(code);

                newValue.set(code, badgeId);
            });

            return newValue;
        });

        // The emulator answers SetActivatedBadges (UserWearBadgeEvent) with a
        // UserBadgesComposer room broadcast, NOT a BadgesEvent — so there is no
        // echo to suppress and the old pendingUpdatesRef counter only ever
        // leaked (incremented on every edit, never decremented), which then
        // silently dropped legitimate later BadgesEvent updates. The server is
        // authoritative here (edits are already persisted), so always apply it.
        // A set the user changed and is not sent yet wins over this older one.
        if (!wearSender.isPending()) setActiveBadgeCodes(toFixedSlots(parser.getActiveBadgeCodes()));

        setBadgeCodes(allBadgeCodes);
    });

    useMessageEvent<BadgeReceivedEvent>(BadgeReceivedEvent, (event) => {
        const parser = event.getParser();

        rememberReceivedBadgeRarity(parser);

        const unseen = isUnseen(UnseenItemCategory.BADGE, parser.badgeId);

        setBadgeCodes((prevValue) => {
            const newValue = [...prevValue];

            if (unseen) newValue.unshift(parser.badgeCode);
            else newValue.push(parser.badgeCode);

            return newValue;
        });

        setBadgeIds((prevValue) => {
            const newValue = new Map(prevValue);

            newValue.set(parser.badgeCode, parser.badgeId);

            return newValue;
        });
    });

    useEffect(() => {
        if (!badgeCodes || !badgeCodes.length) return;

        setSelectedBadgeCode((prevValue) => {
            let newValue = prevValue;

            if (newValue && badgeCodes.indexOf(newValue) === -1) newValue = null;

            if (!newValue) newValue = badgeCodes[0];

            return newValue;
        });
    }, [badgeCodes]);

    useEffect(() => {
        if (!isVisible) return;

        return () => {
            resetCategory(UnseenItemCategory.BADGE);
        };
    }, [isVisible, resetCategory]);

    useEffect(() => {
        if (!isVisible || !needsUpdate) return;

        SendMessageComposer(new RequestBadgesComposer());

        setNeedsUpdate(false);
    }, [isVisible, needsUpdate]);

    const setBadgeAtSlot = (badgeCode: string, slotIndex: number) => {
        const slots = toFixedSlots(activeBadgeCodesRef.current);

        // Remove badge if already in another slot
        const existingIndex = slots.indexOf(badgeCode);
        if (existingIndex >= 0) slots[existingIndex] = null;

        // Place badge at target slot
        slots[slotIndex] = badgeCode;

        wearBadges(slots);
    };

    const removeBadge = (badgeCode: string) => {
        const slots = toFixedSlots(activeBadgeCodesRef.current);
        const index = slots.indexOf(badgeCode);
        if (index === -1) return;

        slots[index] = null;

        wearBadges(slots);
    };

    const reorderBadges = (fromIndex: number, toIndex: number) => {
        if (fromIndex === toIndex) return;

        const slots = toFixedSlots(activeBadgeCodesRef.current);
        const temp = slots[fromIndex];
        slots[fromIndex] = slots[toIndex];
        slots[toIndex] = temp;

        wearBadges(slots);
    };

    const swapBadges = (fromIndex: number, toIndex: number) => {
        if (fromIndex === toIndex) return;

        const slots = toFixedSlots(activeBadgeCodesRef.current);
        const temp = slots[fromIndex];
        slots[fromIndex] = slots[toIndex];
        slots[toIndex] = temp;

        wearBadges(slots);
    };

    const requestBadges = () => {
        SendMessageComposer(new RequestBadgesComposer());
    };

    return {
        badgeCodes,
        activeBadgeCodes,
        selectedBadgeCode,
        setSelectedBadgeCode,
        isWearingBadge,
        canWearBadges,
        toggleBadge,
        getBadgeId,
        setBadgeAtSlot,
        removeBadge,
        reorderBadges,
        swapBadges,
        requestBadges,
        activate,
        deactivate
    };
};

export const useInventoryBadges = () => useSharedHook(useInventoryBadgesState);

registerSharedHook(useInventoryBadgesState);
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser