5180 fichiers 38.5 Mo 0 téléchargements 350 vues mis à jour il y a 7 heures ui
nitro-v36-with-renderer-v36 / src / components / wired / views / extras / WiredExtraChestItemTypeScannerView.tsx
WiredExtraChestItemTypeScannerView.tsx 11.9 Ko Télécharger le fichier
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api';
import { Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { FURNI_SOURCES, sortWiredSourceOptions, WiredSourceOption } from '../WiredSourcesSelector';
import { variableTokenFromId } from '../WiredValueOrVariableField';
import { toWiredVariableId } from '../WiredVariableIds';
import { WiredVariablePicker } from '../WiredVariablePicker';
import { buildWiredVariablePickerEntries, getCustomVariableItemId, IWiredVariablePickerEntry } from '../WiredVariablePickerData';
import { WiredExtraBaseView } from './WiredExtraBaseView';
import { Radio } from '../../../../common/Radio';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';

const SOURCE_SELECTED = 100;
const SOURCE_SECONDARY_SELECTED = 101;
const SCAN_MODES = [0, 1];

/** The chests take the second pick list (source 101), as AIR's dual furni picking does. */
const CHEST_SOURCES: WiredSourceOption[] = sortWiredSourceOptions(
    [
        { value: 0, label: 'wiredfurni.params.sources.furni.0' },
        { value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' },
        { value: 200, label: 'wiredfurni.params.sources.furni.200' },
        { value: 201, label: 'wiredfurni.params.sources.furni.201' }
    ],
    'furni'
);

type SelectionMode = 'types' | 'chests';

interface IScannerVariableCandidate {
    itemId: number;
    hasValue?: boolean;
    isReadOnly?: boolean;
    requiresExecutionContext?: boolean;
    valueShape?: string;
    unavailable?: boolean;
}

/** AIR's filter: a context variable with a value that the room's wired may create, delete and write. */
export const isChestScannerVariableCandidate = (definition: IScannerVariableCandidate): boolean =>
    !!definition &&
    definition.itemId > 0 &&
    !!definition.hasValue &&
    !definition.isReadOnly &&
    !definition.requiresExecutionContext &&
    definition.valueShape !== 'array' &&
    !definition.unavailable;

/**
 * The official chest item type scanner (AIR `§_-i2B§`, addon 18): the usage note, a context variable for the count
 * and the collapsed scan options. Ints `[mode]` (0 every item in the chests, 1 only the displayed ones), the
 * variable as the first variable id; furni slot 0 the item types (first picks), slot 1 the chests (second picks).
 */
export const WiredExtraChestItemTypeScannerView: FC<{}> = () => {
    const { trigger = null, furniIds = [], setFurniIds, setFurniIds2, setIntParams, setStringParam, setVariableIds, setSourceTypes, setAllowsFurni } = useWired();
    const { contextVariableDefinitions = [] } = useWiredTools();
    const [variableToken, setVariableToken] = useState('');
    const [mode, setMode] = useState(0);
    const [typeSource, setTypeSource] = useState(SOURCE_SELECTED);
    const [chestSource, setChestSource] = useState(SOURCE_SECONDARY_SELECTED);
    const [typeIds, setTypeIds] = useState<number[]>([]);
    const [chestIds, setChestIds] = useState<number[]>([]);
    const [selectionMode, setSelectionMode] = useState<SelectionMode>('types');
    const highlightedIds = useRef<number[]>([]);

    const variableEntries = useMemo(
        () =>
            buildWiredVariablePickerEntries('context', 'change-reference', contextVariableDefinitions.filter(isChestScannerVariableCandidate)).filter(
                (entry) => entry.kind === 'custom'
            ),
        [contextVariableDefinitions]
    );

    const syncHighlights = useCallback((nextTypeIds: number[], nextChestIds: number[]) => {
        if (highlightedIds.current.length) {
            WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
            WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
        }

        const chestSet = new Set(nextChestIds);
        const typeOnlyIds = nextTypeIds.filter((id) => !chestSet.has(id));

        if (typeOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(typeOnlyIds);
        if (nextChestIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextChestIds);

        highlightedIds.current = Array.from(new Set([...nextTypeIds, ...nextChestIds]));
    }, []);

    const switchSelection = useCallback(
        (next: SelectionMode) => {
            if (next === 'types' && typeSource !== SOURCE_SELECTED) return;
            if (next === 'chests' && chestSource !== SOURCE_SECONDARY_SELECTED) return;

            const nextTypeIds = selectionMode === 'types' ? [...furniIds] : [...typeIds];
            const nextChestIds = selectionMode === 'chests' ? [...furniIds] : [...chestIds];

            setTypeIds(nextTypeIds);
            setChestIds(nextChestIds);
            setSelectionMode(next);
            setFurniIds([...(next === 'types' ? nextTypeIds : nextChestIds)]);
        },
        [selectionMode, furniIds, typeSource, chestSource, typeIds, chestIds, setFurniIds]
    );

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

        const slots = trigger.furniSourceTypes ?? [];
        const nextTypeIds = [...(trigger.selectedItems ?? [])];
        const nextChestIds = [...(trigger.selectedItems2 ?? [])];

        setMode(trigger.intData?.[0] === 1 ? 1 : 0);
        setVariableToken(variableTokenFromId(trigger.variableIds?.[0] ?? ''));
        setTypeSource(slots.length >= 1 ? slots[0] : SOURCE_SELECTED);
        setChestSource(slots.length >= 2 ? (slots[1] === SOURCE_SELECTED ? SOURCE_SECONDARY_SELECTED : slots[1]) : SOURCE_SECONDARY_SELECTED);
        setTypeIds(nextTypeIds);
        setChestIds(nextChestIds);
        setSelectionMode('types');
        setFurniIds([...nextTypeIds]);
    }, [trigger, setFurniIds]);

    useEffect(() => {
        if (selectionMode === 'types') setTypeIds(furniIds);
        else setChestIds(furniIds);
    }, [furniIds, selectionMode]);

    useEffect(() => {
        syncHighlights(typeIds, chestIds);
    }, [typeIds, chestIds, syncHighlights]);

    useEffect(() => {
        const canEditTypes = typeSource === SOURCE_SELECTED;
        const canEditChests = chestSource === SOURCE_SECONDARY_SELECTED;

        if (selectionMode === 'types' && !canEditTypes && canEditChests) {
            switchSelection('chests');
            return;
        }

        if (selectionMode === 'chests' && !canEditChests && canEditTypes) {
            switchSelection('types');
            return;
        }

        const canEditCurrent = selectionMode === 'types' ? canEditTypes : canEditChests;

        setAllowsFurni(canEditCurrent ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE);
    }, [selectionMode, typeSource, chestSource, switchSelection, setAllowsFurni]);

    useEffect(() => {
        return () => {
            if (!highlightedIds.current.length) return;

            WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
            WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
            highlightedIds.current = [];
        };
    }, []);

    const save = () => {
        const nextTypeIds = selectionMode === 'types' ? [...furniIds] : [...typeIds];
        const nextChestIds = selectionMode === 'chests' ? [...furniIds] : [...chestIds];
        const variableId = getCustomVariableItemId(variableToken);

        setTypeIds(nextTypeIds);
        setChestIds(nextChestIds);

        if (selectionMode === 'chests') {
            setSelectionMode('types');
            setFurniIds([...nextTypeIds]);
        }

        setIntParams([mode]);
        setStringParam('');
        // The variable list's id of the context variable (ctx:12), as AIR sends it.
        setVariableIds(variableId > 0 ? [toWiredVariableId('context', variableToken)] : []);
        setSourceTypes({ furni: [typeSource, chestSource], users: [] });
        setFurniIds2(chestSource === SOURCE_SECONDARY_SELECTED ? nextChestIds.filter((id) => id > 0) : []);
    };

    const validate = () => getCustomVariableItemId(variableToken) > 0;
    const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;

    // AIR forceHidePickFurniInstructions: the two source rows are the whole selection, in the advanced section that
    // addon 18 always shows open (advancedAlwaysVisible).
    return (
        <NativeTextTree>
            <WiredExtraBaseView
                hasSpecialInput={true}
                requiresFurni={WiredFurniType.STUFF_SELECTION_OPTION_BY_ID}
                save={save}
                validate={validate}
                cardStyle={{ width: 400 }}
                showSelection={false}
                footer={
                    <div className="flex flex-col gap-2">
                        <WiredFurniSelectionSourceRow
                            title="wiredfurni.params.sources.furni.title.item_types"
                            options={FURNI_SOURCES}
                            value={typeSource}
                            selectionKind="primary"
                            selectionActive={selectionMode === 'types'}
                            selectionCount={typeIds.length}
                            selectionLimit={selectionLimit}
                            selectionEnabledValues={[SOURCE_SELECTED]}
                            onChange={setTypeSource}
                            onSelectionActivate={() => switchSelection('types')}
                        />
                        <WiredFurniSelectionSourceRow
                            title="wiredfurni.params.sources.furni.title.chests"
                            options={CHEST_SOURCES}
                            value={chestSource}
                            selectionKind="secondary"
                            selectionActive={selectionMode === 'chests'}
                            selectionCount={chestIds.length}
                            selectionLimit={selectionLimit}
                            selectionEnabledValues={[SOURCE_SECONDARY_SELECTED]}
                            onChange={(value) => setChestSource(value === SOURCE_SELECTED ? SOURCE_SECONDARY_SELECTED : value)}
                            onSelectionActivate={() => switchSelection('chests')}
                        />
                    </div>
                }
            >
                <div className="flex flex-col gap-2">
                    <Text small className="whitespace-pre-line">{LocalizeText('wiredfurni.params.chest_item_type_scanner.info').replace(/\\n/g, '\n')}</Text>
                    <div className="flex flex-col gap-1">
                        <Text>{LocalizeText('wiredfurni.params.variables.variable_selection')}</Text>
                        <WiredVariablePicker
                            entries={variableEntries as IWiredVariablePickerEntry[]}
                            recentScope="chest-item-type-scanner"
                            selectedToken={variableToken}
                            onSelect={(entry) => setVariableToken(entry.token)}
                        />
                    </div>
                    <details>
                        <summary>
                            <Text bold>{LocalizeText('wiredfurni.params.chest_item_type_scanner')}</Text>
                        </summary>
                        <div className="flex flex-col gap-1 pt-1">
                            {SCAN_MODES.map((value) => (
                                <label key={value} className="flex items-center gap-1">
                                    <Radio name="chestItemTypeScannerMode" checked={mode === value} onCheckedChange={ () => setMode(value) } />
                                    <Text>{LocalizeText(`wiredfurni.params.chest_item_type_scanner.${value}`)}</Text>
                                </label>
                            ))}
                        </div>
                    </details>
                </div>
            </WiredExtraBaseView>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
3En 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