mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
feat(wired-ui): add altitude and relative move actions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { WiredActionLayoutCode } from '../../../../api';
|
||||
import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView';
|
||||
import { WiredActionSetAltitudeView } from './WiredActionSetAltitudeView';
|
||||
import { WiredActionSendSignalView } from './WiredActionSendSignalView';
|
||||
import { WiredActionFurniAreaView } from '../selectors/WiredActionFurniAreaView';
|
||||
import { WiredSelectorFurniNeighborhoodView } from '../selectors/WiredSelectorFurniNeighborhoodView';
|
||||
@@ -26,6 +27,7 @@ import { WiredActionMoveAndRotateFurniView } from './WiredActionMoveAndRotateFur
|
||||
import { WiredActionMoveFurniToView } from './WiredActionMoveFurniToView';
|
||||
import { WiredActionMoveFurniView } from './WiredActionMoveFurniView';
|
||||
import { WiredActionMuteUserView } from './WiredActionMuteUserView';
|
||||
import { WiredActionRelativeMoveView } from './WiredActionRelativeMoveView';
|
||||
import { WiredActionResetView } from './WiredActionResetView';
|
||||
import { WiredActionSetFurniStateToView } from './WiredActionSetFurniStateToView';
|
||||
import { WiredActionTeleportView } from './WiredActionTeleportView';
|
||||
@@ -57,6 +59,8 @@ export const WiredActionLayoutView = (code: number) =>
|
||||
return <WiredActionChatView />;
|
||||
case WiredActionLayoutCode.FLEE:
|
||||
return <WiredActionFleeView />;
|
||||
case WiredActionLayoutCode.SET_ALTITUDE:
|
||||
return <WiredActionSetAltitudeView />;
|
||||
case WiredActionLayoutCode.GIVE_REWARD:
|
||||
return <WiredActionGiveRewardView />;
|
||||
case WiredActionLayoutCode.GIVE_SCORE:
|
||||
@@ -77,6 +81,8 @@ export const WiredActionLayoutView = (code: number) =>
|
||||
return <WiredActionMoveFurniToView />;
|
||||
case WiredActionLayoutCode.MUTE_USER:
|
||||
return <WiredActionMuteUserView />;
|
||||
case WiredActionLayoutCode.RELATIVE_MOVE:
|
||||
return <WiredActionRelativeMoveView />;
|
||||
case WiredActionLayoutCode.RESET:
|
||||
return <WiredActionResetView />;
|
||||
case WiredActionLayoutCode.SET_FURNI_STATE:
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Slider, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
|
||||
const MAX_DISTANCE = 20;
|
||||
|
||||
const HORIZONTAL_OPTIONS = [
|
||||
{ value: 0, icon: <FaArrowLeft /> },
|
||||
{ value: 1, icon: <FaArrowRight /> }
|
||||
];
|
||||
|
||||
const VERTICAL_OPTIONS = [
|
||||
{ value: 0, icon: <FaArrowDown /> },
|
||||
{ value: 1, icon: <FaArrowUp /> }
|
||||
];
|
||||
|
||||
const normalizeDirection = (value: number, fallback = 1) =>
|
||||
{
|
||||
if(value === 0 || value === 1) return value;
|
||||
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const normalizeDistance = (value: number) =>
|
||||
{
|
||||
if(isNaN(value)) return 0;
|
||||
|
||||
return Math.max(0, Math.min(MAX_DISTANCE, value));
|
||||
};
|
||||
|
||||
export const WiredActionRelativeMoveView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
const [horizontalDirection, setHorizontalDirection] = useState(1);
|
||||
const [horizontalDistance, setHorizontalDistance] = useState(0);
|
||||
const [verticalDirection, setVerticalDirection] = useState(1);
|
||||
const [verticalDistance, setVerticalDistance] = useState(0);
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 4) return trigger.intData[4];
|
||||
return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setHorizontalDirection((trigger.intData.length > 0) ? normalizeDirection(trigger.intData[0], 1) : 1);
|
||||
setHorizontalDistance((trigger.intData.length > 1) ? normalizeDistance(trigger.intData[1]) : 0);
|
||||
setVerticalDirection((trigger.intData.length > 2) ? normalizeDirection(trigger.intData[2], 1) : 1);
|
||||
setVerticalDistance((trigger.intData.length > 3) ? normalizeDistance(trigger.intData[3]) : 0);
|
||||
|
||||
if(trigger.intData.length > 4) setFurniSource(trigger.intData[4]);
|
||||
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = () => setIntParams([
|
||||
horizontalDirection,
|
||||
horizontalDistance,
|
||||
verticalDirection,
|
||||
verticalDistance,
|
||||
furniSource
|
||||
]);
|
||||
|
||||
return (
|
||||
<WiredActionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.movement.horizontal.selection') }</Text>
|
||||
<div className="flex gap-2">
|
||||
{ HORIZONTAL_OPTIONS.map(option =>
|
||||
{
|
||||
return (
|
||||
<label key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (horizontalDirection === option.value) } className="form-check-input" name="relativeMoveHorizontal" type="radio" onChange={ () => setHorizontalDirection(option.value) } />
|
||||
<Text>{ option.icon }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<Text>{ LocalizeText('wiredfurni.params.movement.horizontal.distance', [ 'distance' ], [ horizontalDistance.toString() ]) }</Text>
|
||||
<Slider
|
||||
max={ MAX_DISTANCE }
|
||||
min={ 0 }
|
||||
step={ 1 }
|
||||
value={ horizontalDistance }
|
||||
onChange={ value => setHorizontalDistance(value as number) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.movement.vertical.selection') }</Text>
|
||||
<div className="flex gap-2">
|
||||
{ VERTICAL_OPTIONS.map(option =>
|
||||
{
|
||||
return (
|
||||
<label key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (verticalDirection === option.value) } className="form-check-input" name="relativeMoveVertical" type="radio" onChange={ () => setVerticalDirection(option.value) } />
|
||||
<Text>{ option.icon }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<Text>{ LocalizeText('wiredfurni.params.movement.vertical.distance', [ 'distance' ], [ verticalDistance.toString() ]) }</Text>
|
||||
<Slider
|
||||
max={ MAX_DISTANCE }
|
||||
min={ 0 }
|
||||
step={ 1 }
|
||||
value={ verticalDistance }
|
||||
onChange={ value => setVerticalDistance(value as number) } />
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Slider, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
|
||||
const MIN_ALTITUDE = 0;
|
||||
const MAX_ALTITUDE = 40;
|
||||
const ALTITUDE_STEP = 0.01;
|
||||
const ALTITUDE_PATTERN = /^\d*(\.\d{0,2})?$/;
|
||||
|
||||
const clampAltitude = (value: number) =>
|
||||
{
|
||||
if(isNaN(value)) return MIN_ALTITUDE;
|
||||
|
||||
const clamped = Math.min(MAX_ALTITUDE, Math.max(MIN_ALTITUDE, value));
|
||||
|
||||
return parseFloat(clamped.toFixed(2));
|
||||
};
|
||||
|
||||
const formatAltitude = (value: number) =>
|
||||
{
|
||||
const normalized = clampAltitude(value);
|
||||
const text = normalized.toFixed(2);
|
||||
|
||||
return text.replace(/\.00$/, '').replace(/(\.\d)0$/, '$1');
|
||||
};
|
||||
|
||||
const parseAltitude = (value: string) =>
|
||||
{
|
||||
if(!value || !value.trim().length) return 0;
|
||||
|
||||
const parsed = parseFloat(value);
|
||||
|
||||
if(isNaN(parsed)) return 0;
|
||||
|
||||
return clampAltitude(parsed);
|
||||
};
|
||||
|
||||
const OPERATOR_OPTIONS = [
|
||||
{ value: 0, label: 'wiredfurni.params.operator.0' },
|
||||
{ value: 1, label: 'wiredfurni.params.operator.1' },
|
||||
{ value: 2, label: 'wiredfurni.params.operator.2' }
|
||||
];
|
||||
|
||||
const normalizeOperator = (value: number) =>
|
||||
{
|
||||
if(value < 0 || value > 2) return 2;
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const WiredActionSetAltitudeView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
|
||||
|
||||
const [ operator, setOperator ] = useState(2);
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 1) return trigger.intData[1];
|
||||
return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
|
||||
});
|
||||
const [ altitude, setAltitude ] = useState(0);
|
||||
const [ altitudeInput, setAltitudeInput ] = useState('0');
|
||||
|
||||
const normalizedAltitudeText = useMemo(() => formatAltitude(altitude), [ altitude ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setOperator((trigger.intData.length > 0) ? normalizeOperator(trigger.intData[0]) : 2);
|
||||
setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0));
|
||||
|
||||
const nextAltitude = parseAltitude(trigger.stringData);
|
||||
setAltitude(nextAltitude);
|
||||
setAltitudeInput(formatAltitude(nextAltitude));
|
||||
}, [ trigger ]);
|
||||
|
||||
const updateAltitude = (value: number) =>
|
||||
{
|
||||
const nextValue = clampAltitude(value);
|
||||
|
||||
setAltitude(nextValue);
|
||||
setAltitudeInput(formatAltitude(nextValue));
|
||||
};
|
||||
|
||||
const updateAltitudeInput = (value: string) =>
|
||||
{
|
||||
if(!ALTITUDE_PATTERN.test(value)) return;
|
||||
|
||||
setAltitudeInput(value);
|
||||
|
||||
if(!value.length)
|
||||
{
|
||||
setAltitude(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedValue = parseFloat(value);
|
||||
|
||||
if(isNaN(parsedValue)) return;
|
||||
|
||||
if(parsedValue > MAX_ALTITUDE)
|
||||
{
|
||||
updateAltitude(MAX_ALTITUDE);
|
||||
return;
|
||||
}
|
||||
|
||||
setAltitude(clampAltitude(parsedValue));
|
||||
};
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
operator,
|
||||
furniSource
|
||||
]);
|
||||
|
||||
setStringParam(normalizedAltitudeText);
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredActionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
{ OPERATOR_OPTIONS.map(option =>
|
||||
{
|
||||
return (
|
||||
<div key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (operator === option.value) } className="form-check-input" id={ `setAltitudeOperator${ option.value }` } name="setAltitudeOperator" type="radio" onChange={ () => setOperator(option.value) } />
|
||||
<Text>{ LocalizeText(option.label) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.setaltitude') }</Text>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
inputMode="decimal"
|
||||
type="text"
|
||||
value={ altitudeInput }
|
||||
onBlur={ () => setAltitudeInput(formatAltitude(altitude)) }
|
||||
onChange={ event => updateAltitudeInput(event.target.value) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Slider
|
||||
max={ MAX_ALTITUDE }
|
||||
min={ MIN_ALTITUDE }
|
||||
step={ ALTITUDE_STEP }
|
||||
value={ altitude }
|
||||
onChange={ event => updateAltitude(event as number) } />
|
||||
<Text small>{ normalizedAltitudeText }</Text>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user