5180 fichiers 38.5 Mo 0 téléchargements 352 vues mis à jour il y a 7 heures ui
nitro-v36-with-renderer-v36 / src / components / wired / views / actions / WiredActionInitTransactionView.tsx
WiredActionInitTransactionView.tsx 15.7 Ko Télécharger le fichier
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { localizeWithFallback, WiredFurniType, WiredSelectionVisualizer } from '../../../../api';
import { Text } from '../../../../common';
import { Checkbox } from '../../../../common/Checkbox';
import { NumberInput } from '../../../../common/NumberInput';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';
import { Radio } from '../../../../common/Radio';
import { useWired } from '../../../../hooks';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { FURNI_SOURCES, sortWiredSourceOptions, WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
import { variableTokenFromId, WIRED_FIXED_HOLDER_SOURCE, WiredValueOrVariableField } from '../WiredValueOrVariableField';
import { toWiredVariableIdOnTarget } from '../WiredVariableIds';
import { getCustomVariableItemId } from '../WiredVariablePickerData';
import { WiredActionBaseView } from './WiredActionBaseView';

export const TRANSACTION_MODE_NORMAL = 0;
export const TRANSACTION_MODE_MULTIPLIER = 1;
export const TRANSACTION_MODE_AUTO = 2;
export const TRANSACTION_MULTIPLIER_MIN = 1;
export const TRANSACTION_MULTIPLIER_MAX = 500;
export const TRANSACTION_TIMEOUT_MIN = 30;
export const TRANSACTION_TIMEOUT_MAX = 3600;
export const TRANSACTION_TIMEOUT_DEFAULT = 300;

const GLOBAL_TARGET = -10;
const CONTEXT_TARGET = -20;
const SOURCE_SELECTED = 100;
const SOURCE_SECONDARY_SELECTED = 101;
const REFERENCE_SOURCE = 200;
const MODES = [TRANSACTION_MODE_NORMAL, TRANSACTION_MODE_MULTIPLIER, TRANSACTION_MODE_AUTO];
const MODE_LABELS = ['Normal', 'Multiplier', 'Auto multiplier'];

/** The contracts take the second pick list (source 101), as AIR's dual furni picking does. */
const CONTRACT_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 = 'chests' | 'contracts';

const clampTimeout = (value: number) =>
    Math.min(TRANSACTION_TIMEOUT_MAX, Math.max(TRANSACTION_TIMEOUT_MIN, Number.isFinite(value) ? Math.round(value) : TRANSACTION_TIMEOUT_DEFAULT));

/**
 * The official Initiate Transaction (47, AIR `chests/class_4135`): the transaction mode (normal, multiplier, auto
 * multiplier), the multiplier as a value or a room/context variable (1..500, the auto limit in mode 2, unused in
 * mode 0) and the timeout (30..3600 s, 300 by default). Ints `[mode, multiplier, from a variable, target, timeout on,
 * timeout seconds]`, the variable as the definition's variable id; furni slot 0 the chests (first picks), slot 1
 * the contracts (second picks), user slot 0 the trading users.
 */
export const WiredActionInitTransactionView: FC<{}> = () => {
    const {
        trigger = null,
        furniIds = [],
        setFurniIds,
        setFurniIds2,
        setIntParams,
        setStringParam,
        setVariableIds,
        setSourceTypes,
        setAllowsFurni
    } = useWired();
    const [mode, setMode] = useState(TRANSACTION_MODE_NORMAL);
    const [multiplier, setMultiplier] = useState('1');
    const [isVariable, setIsVariable] = useState(false);
    const [target, setTarget] = useState(GLOBAL_TARGET);
    const [token, setToken] = useState('');
    const [multiplierValid, setMultiplierValid] = useState(true);
    const [timeoutEnabled, setTimeoutEnabled] = useState(false);
    const [timeoutSeconds, setTimeoutSeconds] = useState(String(TRANSACTION_TIMEOUT_DEFAULT));
    const [chestSource, setChestSource] = useState(SOURCE_SELECTED);
    const [contractSource, setContractSource] = useState(SOURCE_SECONDARY_SELECTED);
    const [userSource, setUserSource] = useState(0);
    const [chestIds, setChestIds] = useState<number[]>([]);
    const [contractIds, setContractIds] = useState<number[]>([]);
    const [selectionMode, setSelectionMode] = useState<SelectionMode>('chests');
    const highlightedIds = useRef<number[]>([]);

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

        const contractSet = new Set(nextContractIds);
        const chestOnlyIds = nextChestIds.filter((id) => !contractSet.has(id));

        if (chestOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(chestOnlyIds);
        if (nextContractIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextContractIds);

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

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

            const nextChestIds = selectionMode === 'chests' ? [...furniIds] : [...chestIds];
            const nextContractIds = selectionMode === 'contracts' ? [...furniIds] : [...contractIds];

            setChestIds(nextChestIds);
            setContractIds(nextContractIds);
            setSelectionMode(next);
            setFurniIds([...(next === 'chests' ? nextChestIds : nextContractIds)]);
        },
        [selectionMode, furniIds, chestSource, contractSource, chestIds, contractIds, setFurniIds]
    );

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

        const data = trigger.intData ?? [];
        const slots = trigger.furniSourceTypes ?? [];
        const nextChestIds = [...(trigger.selectedItems ?? [])];
        const nextContractIds = [...(trigger.selectedItems2 ?? [])];

        setMode(MODES.includes(data[0]) ? data[0] : TRANSACTION_MODE_NORMAL);
        // AIR onEditStart: a variable multiplier shows the value 1 next to it.
        setIsVariable(data[2] === 1);
        setMultiplier(String(data[2] === 1 ? 1 : Math.min(TRANSACTION_MULTIPLIER_MAX, Math.max(TRANSACTION_MULTIPLIER_MIN, data[1] ?? 1))));
        setTarget(data[3] === CONTEXT_TARGET ? CONTEXT_TARGET : GLOBAL_TARGET);
        setToken(data[2] === 1 ? variableTokenFromId(trigger.variableIds?.[0] ?? '') : '');
        setTimeoutEnabled(data[4] === 1);
        setTimeoutSeconds(String(data.length > 5 ? clampTimeout(data[5]) : TRANSACTION_TIMEOUT_DEFAULT));
        setChestSource(slots.length >= 1 ? slots[0] : SOURCE_SELECTED);
        setContractSource(slots.length >= 2 ? (slots[1] === SOURCE_SELECTED ? SOURCE_SECONDARY_SELECTED : slots[1]) : SOURCE_SECONDARY_SELECTED);
        setUserSource(trigger.userSourceTypes?.[0] ?? 0);
        setChestIds(nextChestIds);
        setContractIds(nextContractIds);
        setSelectionMode('chests');
        setFurniIds([...nextChestIds]);
    }, [trigger, setFurniIds]);

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

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

    useEffect(() => {
        const canEditChests = chestSource === SOURCE_SELECTED;
        const canEditContracts = contractSource === SOURCE_SECONDARY_SELECTED;

        if (selectionMode === 'chests' && !canEditChests && canEditContracts) {
            switchSelection('contracts');
            return;
        }

        if (selectionMode === 'contracts' && !canEditContracts && canEditChests) {
            switchSelection('chests');
            return;
        }

        const canEditCurrent = selectionMode === 'chests' ? canEditChests : canEditContracts;

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

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

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

    const multiplierUsed = mode !== TRANSACTION_MODE_NORMAL;

    const save = () => {
        const nextChestIds = selectionMode === 'chests' ? [...furniIds] : [...chestIds];
        const nextContractIds = selectionMode === 'contracts' ? [...furniIds] : [...contractIds];
        const variableId = isVariable ? getCustomVariableItemId(token) : 0;
        const value = Math.min(TRANSACTION_MULTIPLIER_MAX, Math.max(TRANSACTION_MULTIPLIER_MIN, parseInt(multiplier, 10) || 1));

        setChestIds(nextChestIds);
        setContractIds(nextContractIds);

        if (selectionMode === 'contracts') {
            setSelectionMode('chests');
            setFurniIds([...nextChestIds]);
        }

        setIntParams([mode, value, variableId > 0 ? 1 : 0, target, timeoutEnabled ? 1 : 0, clampTimeout(parseInt(timeoutSeconds, 10))]);
        setStringParam('');
        // The variable list's id (room:12 / ctx:12), as AIR sends it.
        setVariableIds(variableId > 0 ? [toWiredVariableIdOnTarget(target, token)] : []);
        setSourceTypes({ furni: [chestSource, contractSource, REFERENCE_SOURCE], users: [userSource, REFERENCE_SOURCE] });
        setFurniIds2(contractSource === SOURCE_SECONDARY_SELECTED ? nextContractIds.filter((id) => id > 0) : []);
    };

    const timeoutValue = parseInt(timeoutSeconds, 10);
    const validate = () =>
        (!multiplierUsed || (multiplierValid && (!isVariable || getCustomVariableItemId(token) > 0))) &&
        (!timeoutEnabled || (timeoutValue >= TRANSACTION_TIMEOUT_MIN && timeoutValue <= TRANSACTION_TIMEOUT_MAX));

    const multiplierTitle =
        mode === TRANSACTION_MODE_AUTO
            ? localizeWithFallback('wiredfurni.params.contract.multiplier_selection2', 'Auto multiplier limit:')
            : localizeWithFallback('wiredfurni.params.contract.multiplier_selection', 'Select multiplier:');
    const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;

    // AIR forceHidePickFurniInstructions and advancedAlwaysVisible: the two source rows are the whole selection.
    return (
        <NativeTextTree>
            <WiredActionBaseView
                hasSpecialInput={true}
                requiresFurni={WiredFurniType.STUFF_SELECTION_OPTION_BY_ID}
                save={save}
                validate={validate}
                footerCollapsible={false}
                footer={<WiredSourcesSelector showUsers={true} userSource={userSource} userSlot={0} onChangeUsers={setUserSource} />}
                selectionPreview={
                    <div className="flex flex-col gap-2">
                        <WiredFurniSelectionSourceRow
                            title="wiredfurni.params.sources.furni.title.chests"
                            options={FURNI_SOURCES}
                            value={chestSource}
                            selectionKind="primary"
                            selectionActive={selectionMode === 'chests'}
                            selectionCount={chestIds.length}
                            selectionLimit={selectionLimit}
                            selectionEnabledValues={[SOURCE_SELECTED]}
                            onChange={setChestSource}
                            onSelectionActivate={() => switchSelection('chests')}
                        />
                        <WiredFurniSelectionSourceRow
                            title="wiredfurni.params.sources.furni.title.contracts"
                            options={CONTRACT_SOURCES}
                            value={contractSource}
                            selectionKind="secondary"
                            selectionActive={selectionMode === 'contracts'}
                            selectionCount={contractIds.length}
                            selectionLimit={selectionLimit}
                            selectionEnabledValues={[SOURCE_SECONDARY_SELECTED]}
                            onChange={(value) => setContractSource(value === SOURCE_SELECTED ? SOURCE_SECONDARY_SELECTED : value)}
                            onSelectionActivate={() => switchSelection('contracts')}
                        />
                    </div>
                }
            >
                <div className="flex flex-col gap-1">
                    <Text bold>{localizeWithFallback('wiredfurni.params.contract.mode', 'Transaction method:')}</Text>
                    {MODES.map((value) => (
                        <label key={value} className="flex items-center gap-1">
                            <Radio name="transactionMode" checked={mode === value} onCheckedChange={() => setMode(value)} />
                            <Text>{localizeWithFallback(`wiredfurni.params.contract.mode.${value}`, MODE_LABELS[value])}</Text>
                        </label>
                    ))}
                </div>
                <div className="flex flex-col gap-1">
                    <Text bold>{multiplierTitle}</Text>
                    {/* AIR class_4135: createValueOrVariableSection(0, global/context, multiplier_selection, 1, 500); the title
                        switches to multiplier_selection2 in auto mode and the section is disabled in normal mode. */}
                    <WiredValueOrVariableField
                        disabled={!multiplierUsed}
                        holderSource={WIRED_FIXED_HOLDER_SOURCE}
                        isVariable={isVariable}
                        label={multiplierTitle}
                        literal={multiplier}
                        max={TRANSACTION_MULTIPLIER_MAX}
                        min={TRANSACTION_MULTIPLIER_MIN}
                        target={target}
                        targets={[GLOBAL_TARGET, CONTEXT_TARGET]}
                        token={token}
                        onIsVariableChange={setIsVariable}
                        onLiteralChange={setMultiplier}
                        onTargetChange={setTarget}
                        onTokenChange={setToken}
                        onValidityChange={setMultiplierValid}
                    />
                </div>
                <div className="flex flex-col gap-1">
                    <Text bold>{localizeWithFallback('wiredfurni.params.contract.timeout', 'Timeout:')}</Text>
                    <label className="flex items-center gap-1">
                        <Checkbox checked={timeoutEnabled} onCheckedChange={(nextChecked) => setTimeoutEnabled(nextChecked)} />
                        <Text>{localizeWithFallback('wiredfurni.params.contract.timeout.desc', 'Cancel a trade automatically on timeout')}</Text>
                    </label>
                    <label className="flex items-center gap-1">
                        <Text>{localizeWithFallback('wiredfurni.params.contract.timeout.selection', 'Timeout (seconds):')}</Text>
                        <NumberInput
                            allowOutOfRange
                            aria-label="timeout seconds"
                            className="form-control form-control-sm"
                            max={TRANSACTION_TIMEOUT_MAX}
                            min={TRANSACTION_TIMEOUT_MIN}
                            value={timeoutSeconds}
                            onBlur={() => setTimeoutSeconds(String(clampTimeout(parseInt(timeoutSeconds, 10))))}
                            onChange={(event) => setTimeoutSeconds(event.target.value)}
                        />
                    </label>
                </div>
            </WiredActionBaseView>
        </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