5180 fichiers 38.5 Mo 0 téléchargements 363 vues mis à jour il y a 7 heures ui
nitro-v36-with-renderer-v36 / src / components / wired / views / extras / WiredExtraMovementCurveView.tsx
WiredExtraMovementCurveView.tsx 13.7 Ko Télécharger le fichier
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, localizeWithFallback, WiredFurniType } from '../../../../api';
import contextVariableIcon from '../../../../assets/images/wired/var/icon_source_context_clean.png';
import furniVariableIcon from '../../../../assets/images/wired/var/icon_source_furni.png';
import globalVariableIcon from '../../../../assets/images/wired/var/icon_source_global.png';
import userVariableIcon from '../../../../assets/images/wired/var/icon_source_user.png';
import { Slider, Text } from '../../../../common';
import { useWired, useWiredTools } from '../../../../hooks';
import { OctaneInput } from '../../../../layout';
import { WiredFurniSelectionSourceRow } from '../WiredFurniSelectionSourceRow';
import { toEditorPickSource, toWirePickSource, VARIABLE_PICK_SOURCE } from '../WiredVariablePickSource';
import { sortWiredSourceOptions, USER_SOURCES, useAvailableUserSources, WiredSourceOption } from '../WiredSourcesSelector';
import { WiredVariablePicker } from '../WiredVariablePicker';
import {
    buildWiredVariablePickerEntries,
    createFallbackVariableEntry,
    flattenWiredVariablePickerEntries,
    normalizeVariableTokenFromWire,
    WiredVariablePickerTarget
} from '../WiredVariablePickerData';
import { readWiredVariableToken, toWiredVariableId, wiredVariableIdList } from '../WiredVariableIds';
import { WiredExtraBaseView } from './WiredExtraBaseView';
import { Radio } from '../../../../common/Radio';
import { Select } from '../../../../common/Select';
import { BaseButton } from '../../../../common/BaseButton';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';

/**
 * Habbo's "movement curve" (wf_xtra_mov_curve): how high the furni the stack moves jump, typed or
 * read from a variable. Our easing curves sit under the advanced options.
 * Official int params: [strength from variable, strength, variable target (official ids)]; ours follow:
 * [curve, intensity]. String param: the variable token; its user and furni source in the official slots.
 */

type VariableTarget = 'user' | 'furni' | 'global' | 'context';

const CURVE_OPTIONS: { value: number; label: string }[] = [
    { value: 7, label: 'Jump' },
    { value: 0, label: 'Linear' },
    { value: 1, label: 'Ease in' },
    { value: 2, label: 'Ease out' },
    { value: 3, label: 'Ease in / out' },
    { value: 4, label: 'Bounce' },
    { value: 5, label: 'Elastic' },
    { value: 6, label: 'Drop' }
];

const CURVE_JUMP = 7;
const CURVE_MAX = 7;
const INTENSITY_DEFAULT = 100;
const STRENGTH_MIN = -1000;
const STRENGTH_MAX = 1000;
const STRENGTH_DEFAULT = 80;
const SOURCE_TRIGGER = 0;

const TARGETS: Array<{ key: VariableTarget; value: number; icon: string }> = [
    { key: 'furni', value: 0, icon: furniVariableIcon },
    { key: 'user', value: 1, icon: userVariableIcon },
    { key: 'global', value: -10, icon: globalVariableIcon },
    { key: 'context', value: -20, icon: contextVariableIcon }
];

const FURNI_SOURCES: WiredSourceOption[] = sortWiredSourceOptions(
    [
        { value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
        { value: VARIABLE_PICK_SOURCE, label: 'wiredfurni.params.sources.furni.100' },
        { value: 200, label: 'wiredfurni.params.sources.furni.200' },
        { value: 201, label: 'wiredfurni.params.sources.furni.201' }
    ],
    'furni'
);
const GLOBAL_SOURCES: WiredSourceOption[] = [{ value: SOURCE_TRIGGER, label: 'wiredfurni.params.sources.global' }];
const CONTEXT_SOURCES: WiredSourceOption[] = [{ value: SOURCE_TRIGGER, label: localizeWithFallback('wiredfurni.params.sources.context', 'Context variables') }];

const clamp = (value: number, min: number, max: number, fallback: number) => (Number.isFinite(value) ? Math.max(min, Math.min(max, Math.round(value))) : fallback);
const targetOf = (value: number): VariableTarget => TARGETS.find((target) => target.value === value)?.key ?? 'user';
const targetValue = (key: VariableTarget) => TARGETS.find((target) => target.key === key)?.value ?? 1;

export const WiredExtraMovementCurveView: FC<{}> = () => {
    const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null, setSourceTypes = null, setVariableIds = null } = useWired();
    const { userVariableDefinitions = [], furniVariableDefinitions = [], roomVariableDefinitions = [], contextVariableDefinitions = [] } = useWiredTools();
    const [curveType, setCurveType] = useState(CURVE_JUMP);
    const [intensity, setIntensity] = useState(INTENSITY_DEFAULT);
    const [strengthInput, setStrengthInput] = useState(String(STRENGTH_DEFAULT));
    const [fromVariable, setFromVariable] = useState(false);
    const [target, setTarget] = useState<VariableTarget>('user');
    const [variableToken, setVariableToken] = useState('');
    const [userSource, setUserSource] = useState(SOURCE_TRIGGER);
    const [furniSource, setFurniSource] = useState(SOURCE_TRIGGER);

    const userSources = sortWiredSourceOptions(useAvailableUserSources(trigger, USER_SOURCES), 'users');

    const definitions =
        target === 'furni'
            ? furniVariableDefinitions
            : target === 'global'
              ? roomVariableDefinitions
              : target === 'context'
                ? contextVariableDefinitions
                : userVariableDefinitions;
    const entries = useMemo(() => {
        const built = buildWiredVariablePickerEntries(target as WiredVariablePickerTarget, 'change-reference', definitions);
        if (!variableToken || flattenWiredVariablePickerEntries(built).some((entry) => entry.token === variableToken)) return built;

        const fallback = createFallbackVariableEntry(target, variableToken);
        return fallback ? [fallback, ...built] : built;
    }, [definitions, target, variableToken]);

    const picksFurni = fromVariable && target === 'furni' && furniSource === VARIABLE_PICK_SOURCE;
    const sourceOptions = target === 'furni' ? FURNI_SOURCES : target === 'global' ? GLOBAL_SOURCES : target === 'context' ? CONTEXT_SOURCES : userSources;
    const sourceValue = target === 'furni' ? furniSource : target === 'user' ? userSource : SOURCE_TRIGGER;

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

        const ints = trigger.intData ?? [];

        setFromVariable(ints.length > 0 && ints[0] === 1);
        setStrengthInput(String(ints.length > 1 ? clamp(ints[1], STRENGTH_MIN, STRENGTH_MAX, STRENGTH_DEFAULT) : STRENGTH_DEFAULT));
        setTarget(targetOf(ints.length > 2 ? ints[2] : 1));
        setCurveType(ints.length > 3 ? clamp(ints[3], 0, CURVE_MAX, CURVE_JUMP) : CURVE_JUMP);
        setIntensity(ints.length > 4 ? clamp(ints[4], 0, 100, INTENSITY_DEFAULT) : INTENSITY_DEFAULT);
        setUserSource(trigger.userSourceTypes?.[0] ?? SOURCE_TRIGGER);
        setFurniSource(toEditorPickSource(trigger.furniSourceTypes?.[0] ?? SOURCE_TRIGGER));
        // AIR: the variable as the first variable id; an older server wrote the token as the string.
        setVariableToken(readWiredVariableToken(trigger.variableIds, 0, normalizeVariableTokenFromWire(trigger.stringData ?? '')));
    }, [trigger]);

    const chooseTarget = (next: VariableTarget) => {
        if (next === target) return;
        if (target === 'furni') setFurniIds([]);

        setTarget(next);
        setVariableToken('');
    };

    const save = () => {
        setIntParams([
            fromVariable ? 1 : 0,
            clamp(parseInt(strengthInput, 10), STRENGTH_MIN, STRENGTH_MAX, STRENGTH_DEFAULT),
            targetValue(target),
            curveType,
            intensity
        ]);
        setSourceTypes({ furni: [toWirePickSource(trigger, furniSource)], users: [userSource] });
        setStringParam('');
        setVariableIds(wiredVariableIdList(fromVariable ? toWiredVariableId(target as WiredVariablePickerTarget, variableToken) : ''));
        if (!picksFurni) setFurniIds([]);
    };

    const isJump = curveType === CURVE_JUMP;

    const footer = (
        <div className="flex flex-col gap-2">
            <div className="flex flex-col gap-1">
                <Text bold>{localizeWithFallback('wiredfurni.params.movement_curve.style', 'Curve style')}</Text>
                <Select className="form-select form-select-sm" value={curveType} onChange={(event) => setCurveType(clamp(parseInt(event.target.value, 10), 0, CURVE_MAX, CURVE_JUMP))}>
                    {CURVE_OPTIONS.map((option) => (
                        <option key={option.value} value={option.value}>
                            {localizeWithFallback(`wiredfurni.params.movement_curve.style.${option.value}`, option.label)}
                        </option>
                    ))}
                </Select>
            </div>
            {!isJump && curveType > 0 && (
                <div className="flex flex-col gap-1">
                    <Text bold>
                        {localizeWithFallback('wiredfurni.params.movement_curve.intensity', 'Intensity')} ({intensity}%)
                    </Text>
                    <Slider max={100} min={0} step={1} value={intensity} onChange={(value) => setIntensity(clamp(value as number, 0, 100, INTENSITY_DEFAULT))} />
                </div>
            )}
        </div>
    );

    return (
        <NativeTextTree>
            <WiredExtraBaseView
                footer={footer}
                hasSpecialInput={true}
                requiresFurni={picksFurni ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE}
                save={save}
                validate={() => !(isJump && fromVariable && !variableToken)}
            >
                <div className="octane-wired__give-var">
                    <div className={`octane-wired__give-var-section ${isJump ? '' : 'opacity-50 pointer-events-none'}`}>
                        <div className="octane-wired__give-var-section-title">{localizeWithFallback('wiredfurni.params.movement_curve', 'Movement curve:')}</div>
                        <label className="octane-wired__change-var-radio">
                            <Radio checked={!fromVariable} onCheckedChange={() => setFromVariable(false)} />
                            <Text>{LocalizeText('wiredfurni.params.variables.reference_value.set_value')}</Text>
                            <OctaneInput
                                className="octane-wired__give-var-number"
                                max={STRENGTH_MAX}
                                min={STRENGTH_MIN}
                                type="number"
                                value={strengthInput}
                                onChange={(event) => setStrengthInput(event.target.value)}
                            />
                        </label>

                        <div className="octane-wired__change-var-reference-block">
                            <label className="octane-wired__change-var-radio">
                                <Radio checked={fromVariable} onCheckedChange={() => setFromVariable(true)} />
                                <Text>{LocalizeText('wiredfurni.params.variables.reference_value.from_variable')}</Text>
                                <div className="octane-wired__give-var-targets">
                                    {TARGETS.map((button) => (
                                        <BaseButton
                                            key={button.key}
                                            className={`octane-wired__give-var-target octane-wired__give-var-target--${button.key} ${target === button.key ? 'is-active' : ''}`}
                                            disabled={!fromVariable}
                                            type="button"
                                            onClick={() => chooseTarget(button.key)}
                                        >
                                            <img alt={button.key} src={button.icon} />
                                        </BaseButton>
                                    ))}
                                </div>
                            </label>

                            <div className={fromVariable ? '' : 'opacity-50 pointer-events-none'}>
                                <WiredVariablePicker
                                    entries={entries}
                                    recentScope="movement-curve-reference"
                                    selectedToken={variableToken}
                                    onSelect={(entry) => setVariableToken(entry.token)}
                                />
                            </div>
                        </div>
                    </div>

                    <div className="octane-wired__divider" />

                    <div className={fromVariable && isJump ? '' : 'opacity-50 pointer-events-none'}>
                        <WiredFurniSelectionSourceRow
                            options={sourceOptions}
                            selectionActive={true}
                            selectionCount={furniIds.length}
                            selectionEnabledValues={[VARIABLE_PICK_SOURCE]}
                            selectionKind="primary"
                            selectionLimit={trigger?.maximumItemSelectionCount ?? 0}
                            showSelectionToggle={false}
                            title="wiredfurni.params.sources.merged.title.variables_reference"
                            value={sourceValue}
                            onChange={(value) => {
                                if (target === 'furni') {
                                    setFurniSource(value);
                                    if (value !== VARIABLE_PICK_SOURCE) setFurniIds([]);
                                    return;
                                }

                                if (target === 'user') setUserSource(value);
                            }}
                        />
                    </div>
                </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