5180 fichiers 38.5 Mo 0 téléchargements 270 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / hooks / collectibles / useCollectibles.ts
useCollectibles.ts 19.5 Ko Télécharger le fichier
import {
    AddLinkEventTracker,
    ClaimCollectibleRewardsComposer,
    ClaimCollectionBonusComposer,
    ClaimCollectionRewardComposer,
    CollectibleClaimAllResultEvent,
    CollectibleCollectionData,
    CollectibleCollectionsEvent,
    CollectibleEmeraldBalanceEvent,
    CollectibleInventoryItemData,
    CollectibleMintableData,
    CollectibleMintingEnabledEvent,
    CollectibleMintResultEvent,
    CollectibleMintTokenOfferData,
    CollectibleMintTokenOffersEvent,
    CollectibleMintTokensEvent,
    CollectibleMintablesEvent,
    CollectibleProductData,
    CollectiblePurchaseResultEvent,
    CollectibleRewardBoxOpenedEvent,
    CollectibleRewardBoxResultEvent,
    CollectibleRewardClaimData,
    CollectibleRewardClaimsEvent,
    CollectibleShopOfferData,
    CollectibleShopOffersEvent,
    CollectibleSilverBalanceEvent,
    CollectibleTransferFeeEvent,
    CollectibleTransferResultEvent,
    CollectibleWalletsEvent,
    CollectiblesInventoryEvent,
    CollectiblesInventoryUpdateEvent,
    CollectionBonusClaimResultEvent,
    CollectionRewardClaimResultEvent,
    CollectorScoreEvent,
    GetCollectibleCollectionsComposer,
    GetCollectibleRewardClaimsComposer,
    GetCollectibleShopComposer,
    GetCollectibleTransferFeeComposer,
    GetCollectibleWalletsComposer,
    GetCollectiblesInventoryComposer,
    GetMintableItemsComposer,
    GetMintingEnabledComposer,
    GetMintTokenOffersComposer,
    GetMintTokensComposer,
    GetRoomEngine,
    GetSessionDataManager,
    ILinkEventTracker,
    MintCollectibleComposer,
    NftEmeraldConvertResultEvent,
    OpenCollectibleRewardBoxComposer,
    PurchaseCollectibleComposer,
    PurchaseMintTokensComposer,
    RemoveLinkEventTracker,
    RoomEngineTriggerWidgetEvent,
    SelectCollectibleWalletComposer,
    TransferCollectiblesComposer
} from '@octane/renderer';
import { useCallback, useEffect, useRef, useState } from 'react';
import { registerSharedHook, useSharedHook } from '@/state/useSharedHook';
import { GetConfigurationValue, IsOwnerOfFurniture, LocalizeBadgeName, localizeWithFallback, NotificationBubbleType, SendMessageComposer } from '../../api';
import { useMessageEvent, useOctaneEvent } from '../events';
import { useNotification } from '../notification';
import { applyCollectiblesUpdate, COLLECTIBLES_LINK_PREFIX, collectibleProductName, CollectiblesTab, PendingClaims, rarityColor } from './collectibles.helpers';
import curatorStampLarge from '../../assets/images/collectibles/collectables_icon_curator_stamp_large.png';

/** Whether the hotel runs collectibles (`collectibles.enabled`, the server needs `hotel.collectibles.enabled`). */
export const isCollectiblesEnabled = (): boolean => GetConfigurationValue<boolean>('collectibles.enabled', false) === true;

/** `CollectiblesController.getProductName`, against the furnidata and texts of this client. */
export const getCollectibleProductName = (product: CollectibleProductData): string =>
    collectibleProductName(product, {
        floorName: (id) => GetSessionDataManager().getFloorItemData(id)?.name ?? null,
        wallName: (id) => GetSessionDataManager().getWallItemData(id)?.name ?? null,
        badgeName: (code) => LocalizeBadgeName(code),
        localize: (key, fallback) => localizeWithFallback(key, fallback)
    });

export interface CollectorScore {
    score: number;
    highestScore: number;
    level: number;
}

/**
 * The collectibles hub and its data (`CollectiblesController`, `CollectiblesView` and its tabs), the
 * collectibles inventory, reward boxes and emerald furni. Every list is the server's answer to its
 * request, kept as it arrives.
 */
const useCollectiblesStore = () => {
    const [isVisible, setIsVisible] = useState(false);
    const [currentTab, setCurrentTab] = useState<CollectiblesTab>('rewards');
    const [collectorWallet, setCollectorWallet] = useState('');
    const [wallets, setWallets] = useState<string[]>(null);
    const [emeralds, setEmeralds] = useState(0);
    const [silver, setSilver] = useState(0);
    const [score, setScore] = useState<CollectorScore>({ score: 0, highestScore: 0, level: 0 });
    const [collections, setCollections] = useState<CollectibleCollectionData[]>(null);
    const [pendingClaims, setPendingClaims] = useState<Record<string, PendingClaims>>({});
    const [shopOffers, setShopOffers] = useState<CollectibleShopOfferData[]>(null);
    const [purchasePending, setPurchasePending] = useState(false);
    const [inventory, setInventory] = useState<CollectibleInventoryItemData[]>(null);
    const [rewardClaims, setRewardClaims] = useState<CollectibleRewardClaimData[]>(null);
    const [claimAllPending, setClaimAllPending] = useState(false);
    const [transferFee, setTransferFee] = useState<number>(null);
    const [transferPending, setTransferPending] = useState(false);
    const [mintingEnabled, setMintingEnabled] = useState(false);
    const [mintables, setMintables] = useState<CollectibleMintableData[]>(null);
    const [mintTokenOffers, setMintTokenOffers] = useState<CollectibleMintTokenOfferData[]>([]);
    const [mintTokens, setMintTokens] = useState<number>(null);
    const [mintPending, setMintPending] = useState(false);
    const [rewardQueue, setRewardQueue] = useState<CollectibleProductData[]>([]);
    const purchaseNameRef = useRef('');
    const visibleRef = useRef(false);

    useEffect(() => {
        visibleRef.current = isVisible;
    }, [isVisible]);
    const { simpleAlert = null, showSingleBubble = null, showConfirm = null } = useNotification();

    const activeWallet = wallets && wallets.length ? wallets[0] : null;

    const notify = useCallback(
        (message: string) => showSingleBubble?.(message, NotificationBubbleType.INFO, curatorStampLarge),
        [showSingleBubble]
    );

    const requestWalletData = useCallback((wallet: string) => {
        SendMessageComposer(new SelectCollectibleWalletComposer(wallet));
        SendMessageComposer(new GetCollectibleCollectionsComposer(wallet));
        SendMessageComposer(new GetCollectibleRewardClaimsComposer(wallet));
        if (GetConfigurationValue<boolean>('nft.minting.enabled', false)) SendMessageComposer(new GetMintTokensComposer(wallet));
    }, []);

    /** `CollectiblesView` constructor: every tab asks for its data when the hub opens. */
    const openHub = useCallback((tab: CollectiblesTab = null) => {
        if (!isCollectiblesEnabled()) return;

        if (tab) setCurrentTab(tab);
        setIsVisible(true);
        SendMessageComposer(new GetCollectibleWalletsComposer());
        if (GetConfigurationValue<boolean>('nft.shop.enabled', false)) SendMessageComposer(new GetCollectibleShopComposer());
        if (GetConfigurationValue<boolean>('collectibles.transfer.enabled', false)) SendMessageComposer(new GetCollectibleTransferFeeComposer());
        if (GetConfigurationValue<boolean>('nft.minting.enabled', false)) {
            SendMessageComposer(new GetMintableItemsComposer());
            SendMessageComposer(new GetMintingEnabledComposer());
            SendMessageComposer(new GetMintTokenOffersComposer());
        }
    }, []);

    useEffect(() => {
        const linkTracker: ILinkEventTracker = {
            linkReceived: (url: string) => {
                const parts = url.split('/');

                if (parts.length < 2) return;

                if (parts[1] === 'open') openHub((parts[2] as CollectiblesTab) || null);
                if (parts[1] === 'close') setIsVisible(false);
                if (parts[1] === 'toggle') {
                    if (visibleRef.current) setIsVisible(false);
                    else openHub();
                }
            },
            eventUrlPrefix: COLLECTIBLES_LINK_PREFIX
        };

        AddLinkEventTracker(linkTracker);

        return () => RemoveLinkEventTracker(linkTracker);
    }, [openHub]);

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

        setCollectorWallet(parser.collectorWallet);
        setWallets([...parser.wallets]);

        if (parser.wallets.length) requestWalletData(parser.wallets[0]);
    });

    useMessageEvent<CollectibleEmeraldBalanceEvent>(CollectibleEmeraldBalanceEvent, (event) => setEmeralds(event.getParser().amount));

    useMessageEvent<CollectibleSilverBalanceEvent>(CollectibleSilverBalanceEvent, (event) => setSilver(event.getParser().amount));

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

        setScore({ score: parser.score, highestScore: parser.highestScore, level: parser.level });
    });

    useMessageEvent<CollectibleCollectionsEvent>(CollectibleCollectionsEvent, (event) => {
        setCollections([...event.getParser().collections]);
        setPendingClaims({});
    });

    const onClaimResult = (collectionId: string, wallet: string, success: boolean) => {
        notify(success
            ? localizeWithFallback('collectibles.claiming.success', 'Reward claimed!')
            : localizeWithFallback('collectibles.claiming.failed', 'Claim failed, please try again.', ['id'], ['']));

        if (wallet && wallet === activeWallet) SendMessageComposer(new GetCollectibleCollectionsComposer(wallet));
        else setPendingClaims((current) => ({ ...current, [collectionId]: { bonus: false, reward: false } }));
    };

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

        onClaimResult(parser.collectionId, parser.wallet, parser.success);
    });

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

        onClaimResult(parser.collectionId, parser.wallet, parser.success);
    });

    useMessageEvent<CollectibleShopOffersEvent>(CollectibleShopOffersEvent, (event) => setShopOffers([...event.getParser().offers]));

    useMessageEvent<CollectiblePurchaseResultEvent>(CollectiblePurchaseResultEvent, (event) => {
        setPurchasePending(false);

        if (event.getParser().result === 1) {
            simpleAlert?.(
                localizeWithFallback('notification.nft.purchase.error', 'An error occurred during the payment'),
                null,
                null,
                null,
                localizeWithFallback('catalog.alert.purchaseerror.title', 'Purchase error')
            );
            return;
        }

        const name = purchaseNameRef.current || 'unknown';

        notify(localizeWithFallback('notifications.text.purchase.ok', `Purchased ${name}`, ['productName'], [name]));
    });

    useMessageEvent<CollectiblesInventoryEvent>(CollectiblesInventoryEvent, (event) => setInventory([...event.getParser().items]));

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

        setInventory((current) => (current ? applyCollectiblesUpdate(current, parser.added, parser.removed) : current));

        if (activeWallet) SendMessageComposer(new GetCollectibleCollectionsComposer(activeWallet));
    });

    useMessageEvent<CollectibleTransferFeeEvent>(CollectibleTransferFeeEvent, (event) => setTransferFee(event.getParser().amount));

    useMessageEvent<CollectibleTransferResultEvent>(CollectibleTransferResultEvent, (event) => {
        const result = event.getParser().result;

        setTransferPending(false);
        notify(result === 0
            ? localizeWithFallback('collectibles.transfer.success', 'Transfer completed')
            : localizeWithFallback('collectibles.transfer.error', `Something went wrong with the transfer. (Error: ${result})`, ['id'], [String(result)]));

        if (result === 0) SendMessageComposer(new GetCollectibleTransferFeeComposer());
    });

    useMessageEvent<CollectibleRewardClaimsEvent>(CollectibleRewardClaimsEvent, (event) => {
        setRewardClaims(event.getParser().claims.filter((claim) => claim.claimedAmount < claim.claimLimit));
    });

    useMessageEvent<CollectibleClaimAllResultEvent>(CollectibleClaimAllResultEvent, (event) => {
        const success = event.getParser().result === 0;

        setClaimAllPending(false);
        notify(success
            ? localizeWithFallback('collectibles.claiming.success', 'Reward claimed!')
            : localizeWithFallback('collectibles.claiming.failed', 'Claim failed, please try again.', ['id'], [String(event.getParser().result)]));

        if (success) setRewardClaims([]);
    });

    /**
     * `CollectiblesController._rb7f9a9bbfc500a`: a box opened in the room. The opener gets the reward window; anyone
     * else an `nft_opening` bubble with the reward's icon and rarity, as AIR passes them to the notification.
     */
    useMessageEvent<CollectibleRewardBoxOpenedEvent>(CollectibleRewardBoxOpenedEvent, (event) => {
        const parser = event.getParser();

        if (parser.state === 0 || !parser.reward) return;

        if (parser.openerId === GetSessionDataManager().userId) {
            setRewardQueue((queue) => [...queue, parser.reward]);
            return;
        }

        if (!isCollectiblesEnabled()) return;

        showSingleBubble?.(
            localizeWithFallback(
                'collectibles.reward_box.notif.desc',
                `${localizeWithFallback('generic.someone', 'Someone')} received a ${getCollectibleProductName(parser.reward)}`,
                ['name', 'item'],
                [localizeWithFallback('generic.someone', 'Someone'), getCollectibleProductName(parser.reward)]
            ),
            NotificationBubbleType.NFT_OPENING,
            null,
            null,
            '',
            { product: parser.reward, rarity: parser.reward.rarity, rarityColor: rarityColor(parser.reward.rarity) }
        );
    });

    useMessageEvent<CollectibleRewardBoxResultEvent>(CollectibleRewardBoxResultEvent, (event) => {
        const result = event.getParser().result;

        if (result === 1) notify(localizeWithFallback('generic.error', 'Error'));
        if (result === 2) notify(localizeWithFallback('collectibles.reward_box.wrong_wallet', 'To open this reward box, it needs to be in your Collector Wallet.'));
    });

    useMessageEvent<CollectibleMintingEnabledEvent>(CollectibleMintingEnabledEvent, (event) => setMintingEnabled(event.getParser().enabled));

    useMessageEvent<CollectibleMintablesEvent>(CollectibleMintablesEvent, (event) => setMintables([...event.getParser().mintables]));

    useMessageEvent<CollectibleMintTokenOffersEvent>(CollectibleMintTokenOffersEvent, (event) => setMintTokenOffers([...event.getParser().offers]));

    useMessageEvent<CollectibleMintTokensEvent>(CollectibleMintTokensEvent, (event) => setMintTokens(event.getParser().amount));

    useMessageEvent<CollectibleMintResultEvent>(CollectibleMintResultEvent, (event) => {
        setMintPending(false);

        if (event.getParser().result !== 1) notify(localizeWithFallback('shop.minting.failed', 'Collecting failed'));
    });

    useMessageEvent<NftEmeraldConvertResultEvent>(NftEmeraldConvertResultEvent, (event) => {
        const result = event.getParser().result;

        if (result === 0) return;

        notify(result === 5
            ? localizeWithFallback('notification.nft.emerald_convert.not_in_collector', 'To convert this Emerald furni, it needs to be in your Collector Wallet')
            : localizeWithFallback('notification.nft.emerald_convert_failed', 'Converting emeralds failed'));
    });

    const claimCollection = useCallback(
        (collectionId: string, bonus: boolean) => {
            if (!activeWallet) return;

            setPendingClaims((current) => {
                const previous = current[collectionId] ?? { bonus: false, reward: false };

                return { ...current, [collectionId]: bonus ? { ...previous, bonus: true } : { ...previous, reward: true } };
            });

            SendMessageComposer(bonus ? new ClaimCollectionBonusComposer(collectionId, activeWallet) : new ClaimCollectionRewardComposer(collectionId, activeWallet));
        },
        [activeWallet]
    );

    const purchase = useCallback(
        (offer: CollectibleShopOfferData) => {
            if (!offer || purchasePending) return;

            purchaseNameRef.current = getCollectibleProductName(offer.product);
            setPurchasePending(true);
            SendMessageComposer(new PurchaseCollectibleComposer(offer.productCode, activeWallet ?? ''));
        },
        [activeWallet, purchasePending]
    );

    const transfer = useCallback(
        (recipient: string) => {
            if (transferPending || !recipient?.trim().length) return;

            setTransferPending(true);
            SendMessageComposer(new TransferCollectiblesComposer(recipient.trim()));
        },
        [transferPending]
    );

    const claimAllRewards = useCallback(() => {
        if (claimAllPending) return;

        setClaimAllPending(true);
        SendMessageComposer(new ClaimCollectibleRewardsComposer('', ''));
    }, [claimAllPending]);

    const buyMintTokens = useCallback(
        (offer: CollectibleMintTokenOfferData) => {
            if (!offer || !activeWallet || purchasePending) return;

            purchaseNameRef.current = localizeWithFallback('shop.minting.tokens', 'Stamps');
            setPurchasePending(true);
            SendMessageComposer(new PurchaseMintTokensComposer(offer.offerId, activeWallet));
        },
        [activeWallet, purchasePending]
    );

    const mint = useCallback(
        (itemId: number) => {
            if (!activeWallet || mintPending) return;

            setMintPending(true);
            SendMessageComposer(new MintCollectibleComposer(itemId, activeWallet));
        },
        [activeWallet, mintPending]
    );

    const requestInventory = useCallback(() => {
        if (!isCollectiblesEnabled()) return;

        SendMessageComposer(new GetCollectiblesInventoryComposer());
    }, []);

    const openRewardBox = useCallback((objectId: number) => SendMessageComposer(new OpenCollectibleRewardBoxComposer(objectId)), []);

    const dismissReward = useCallback(() => setRewardQueue((queue) => queue.slice(1)), []);

    /** `furniture_nft_reward_box` double-click: the room engine asks to confirm opening the box. */
    useOctaneEvent<RoomEngineTriggerWidgetEvent>(RoomEngineTriggerWidgetEvent.REQUEST_NFT_REWARD_BOX, (event) => {
        if (!isCollectiblesEnabled()) return;

        const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category);

        if (!roomObject || !IsOwnerOfFurniture(roomObject)) return;

        showConfirm?.(
            localizeWithFallback('collectibles.reward_box.confirm_description', 'Do you want to open this reward box?'),
            () => openRewardBox(event.objectId),
            null,
            null,
            null,
            localizeWithFallback('collectibles.reward_box.confirm_title', 'Open the reward box')
        );
    });

    return {
        isVisible,
        currentTab,
        setCurrentTab,
        closeHub: () => setIsVisible(false),
        openHub,
        collectorWallet,
        wallets,
        activeWallet,
        emeralds,
        silver,
        score,
        collections,
        pendingClaims,
        claimCollection,
        shopOffers,
        purchasePending,
        purchase,
        inventory,
        requestInventory,
        rewardClaims,
        claimAllPending,
        claimAllRewards,
        transferFee,
        transferPending,
        transfer,
        mintingEnabled,
        mintables,
        mintTokenOffers,
        mintTokens,
        mintPending,
        buyMintTokens,
        mint,
        rewardQueue,
        dismissReward,
        openRewardBox
    };
};

export const useCollectibles = () => useSharedHook(useCollectiblesStore);

registerSharedHook(useCollectiblesStore);
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