Merge pull request #2 from Lorenzune/pr/wired-source-aware-ui-20260316

feat(wired): update source-aware furni and signal UI
This commit is contained in:
DuckieTM
2026-03-16 16:47:23 +01:00
committed by GitHub
23 changed files with 339 additions and 516 deletions
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { NitroInput } from '../../../../layout'; import { NitroInput } from '../../../../layout';
@@ -9,7 +9,7 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredActionBotMoveView: FC<{}> = props => export const WiredActionBotMoveView: FC<{}> = props =>
{ {
const [ botName, setBotName ] = useState(''); const [ botName, setBotName ] = useState('');
const { trigger = null, furniIds = [], setFurniIds = null, setStringParam = null, setIntParams = null } = useWired(); const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
@@ -33,23 +33,9 @@ export const WiredActionBotMoveView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { NitroInput } from '../../../../layout'; import { NitroInput } from '../../../../layout';
@@ -9,7 +9,7 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredActionBotTeleportView: FC<{}> = props => export const WiredActionBotTeleportView: FC<{}> = props =>
{ {
const [ botName, setBotName ] = useState(''); const [ botName, setBotName ] = useState('');
const { trigger = null, furniIds = [], setFurniIds = null, setStringParam = null, setIntParams = null } = useWired(); const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
@@ -33,23 +33,9 @@ export const WiredActionBotTeleportView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredActionCallAnotherStackView: FC<{}> = props => export const WiredActionCallAnotherStackView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length >= 1) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0];
@@ -21,25 +21,11 @@ export const WiredActionCallAnotherStackView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource ]); const save = () => setIntParams([ furniSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredActionChaseView: FC<{}> = props => export const WiredActionChaseView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length >= 1) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0];
@@ -21,25 +21,11 @@ export const WiredActionChaseView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource ]); const save = () => setIntParams([ furniSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredActionFleeView: FC<{}> = props => export const WiredActionFleeView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length >= 1) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0];
@@ -21,25 +21,11 @@ export const WiredActionFleeView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource ]); const save = () => setIntParams([ furniSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@@ -30,7 +30,7 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props =>
{ {
const [ movement, setMovement ] = useState(-1); const [ movement, setMovement ] = useState(-1);
const [ rotation, setRotation ] = useState(-1); const [ rotation, setRotation ] = useState(-1);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 2) return trigger.intData[2]; if(trigger?.intData?.length > 2) return trigger.intData[2];
@@ -56,23 +56,9 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Slider, Text } from '../../../../common'; import { Slider, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@@ -28,7 +28,7 @@ export const WiredActionMoveFurniToView: FC<{}> = props =>
{ {
const [ spacing, setSpacing ] = useState(-1); const [ spacing, setSpacing ] = useState(-1);
const [ movement, setMovement ] = useState(-1); const [ movement, setMovement ] = useState(-1);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 2) return trigger.intData[2]; if(trigger?.intData?.length > 2) return trigger.intData[2];
@@ -54,23 +54,9 @@ export const WiredActionMoveFurniToView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@@ -42,7 +42,7 @@ export const WiredActionMoveFurniView: FC<{}> = props =>
{ {
const [ movement, setMovement ] = useState(-1); const [ movement, setMovement ] = useState(-1);
const [ rotation, setRotation ] = useState(-1); const [ rotation, setRotation ] = useState(-1);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 2) return trigger.intData[2]; if(trigger?.intData?.length > 2) return trigger.intData[2];
@@ -68,23 +68,9 @@ export const WiredActionMoveFurniView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,185 +1,213 @@
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api'; import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api';
import { Text } from '../../../../common'; import { Button, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredSourcesSelector } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
const ANTENNA_PICKED = 0; const ANTENNA_INTERACTION_TYPES = [ 'antenna' ];
const ANTENNA_TRIGGER = 1;
const FORWARD_NONE = 0; const SOURCE_TRIGGER = 0;
const FORWARD_TRIGGER = 1; const SOURCE_SELECTED = 100;
const FORWARD_SELECTOR = 200;
const FORWARD_SIGNAL = 201;
const FURNI_FORWARD_OPTIONS = [ const FORWARD_ITEM_DELIMITER = ';';
{ value: FORWARD_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
{ value: FORWARD_SELECTOR, label: 'wiredfurni.params.sources.furni.200' },
{ value: FORWARD_SIGNAL, label: 'wiredfurni.params.sources.furni.201' },
];
const USER_FORWARD_OPTIONS = [ type SelectionMode = 'antenna' | 'furni';
{ value: FORWARD_TRIGGER, label: 'wiredfurni.params.sources.users.0' },
{ value: FORWARD_SELECTOR, label: 'wiredfurni.params.sources.users.200' }, const parseForwardIds = (data: string): number[] =>
{ value: FORWARD_SIGNAL, label: 'wiredfurni.params.sources.users.201' }, {
]; if(!data || !data.length) return [];
const ids = new Set<number>();
for(const part of data.split(/[;,\t]/))
{
const trimmed = part.trim();
if(!trimmed.length) continue;
const value = parseInt(trimmed, 10);
if(!isNaN(value) && value > 0) ids.add(value);
}
return Array.from(ids);
};
const serializeForwardIds = (ids: number[]): string =>
{
if(!ids || !ids.length) return '';
return ids.filter(id => (id > 0)).join(FORWARD_ITEM_DELIMITER);
};
export const WiredActionSendSignalView: FC<{}> = () => export const WiredActionSendSignalView: FC<{}> = () =>
{ {
const [ antennaSource, setAntennaSource ] = useState(ANTENNA_PICKED); const [ furniSource, setFurniSource ] = useState<number>(SOURCE_TRIGGER);
const [ furniForward, setFurniForward ] = useState(FORWARD_NONE); const [ userSource, setUserSource ] = useState<number>(SOURCE_TRIGGER);
const [ userForward, setUserForward ] = useState(FORWARD_NONE);
const [ signalPerFurni, setSignalPerFurni ] = useState(false); const [ signalPerFurni, setSignalPerFurni ] = useState(false);
const [ signalPerUser, setSignalPerUser ] = useState(false); const [ signalPerUser, setSignalPerUser ] = useState(false);
const [ showAdvanced, setShowAdvanced ] = useState(false); const [ antennaIds, setAntennaIds ] = useState<number[]>([]);
const [ forwardFurniIds, setForwardFurniIds ] = useState<number[]>([]);
const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('antenna');
const { trigger = null, setIntParams } = useWired(); const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null, setAllowedInteractionTypes = null } = useWired();
useEffect(() => useEffect(() =>
{ {
if(!trigger) return; if(!trigger) return;
const p = trigger.intData; const p = trigger.intData;
if(p.length >= 1) setAntennaSource(p[0]); if(p.length > 1) setFurniSource(p[1]);
if(p.length >= 2) setFurniForward(p[1]); else setFurniSource(SOURCE_TRIGGER);
if(p.length >= 3) setUserForward(p[2]);
if(p.length >= 4) setSignalPerFurni(p[3] === 1);
if(p.length >= 5) setSignalPerUser(p[4] === 1);
if(p.length >= 1 && (p[0] !== ANTENNA_PICKED || p[1] !== FORWARD_NONE || if(p.length > 2) setUserSource(p[2]);
p[2] !== FORWARD_NONE || (p.length >= 4 && p[3] === 1) || (p.length >= 5 && p[4] === 1))) else setUserSource(SOURCE_TRIGGER);
{
setShowAdvanced(true); setSignalPerFurni(p.length > 3 && p[3] === 1);
} setSignalPerUser(p.length > 4 && p[4] === 1);
setAntennaIds(trigger.selectedItems ?? []);
setForwardFurniIds(parseForwardIds(trigger.stringData));
setSelectionMode('antenna');
}, [ trigger ]); }, [ trigger ]);
useEffect(() =>
{
if(selectionMode === 'antenna') setAllowedInteractionTypes(ANTENNA_INTERACTION_TYPES);
else setAllowedInteractionTypes(null);
return () => setAllowedInteractionTypes(null);
}, [ selectionMode, setAllowedInteractionTypes ]);
useEffect(() =>
{
if(selectionMode === 'antenna') setAntennaIds(furniIds);
else setForwardFurniIds(furniIds);
}, [ furniIds, selectionMode ]);
const applySelection = useCallback((nextIds: number[]) =>
{
if(!setFurniIds) return;
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
if(nextIds && nextIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(nextIds);
return [ ...nextIds ];
});
}, [ setFurniIds ]);
const switchSelection = useCallback((mode: SelectionMode) =>
{
if(mode === selectionMode) return;
if(mode === 'furni' && furniSource !== SOURCE_SELECTED) return;
const nextIds = (mode === 'antenna') ? antennaIds : forwardFurniIds;
applySelection(nextIds);
setSelectionMode(mode);
}, [ selectionMode, furniSource, antennaIds, forwardFurniIds, applySelection ]);
const onChangeFurniSource = (next: number) =>
{
if(forwardFurniIds.length) setForwardFurniIds([]);
if(selectionMode === 'furni')
{
applySelection(antennaIds);
setSelectionMode('antenna');
}
setFurniSource(next);
};
const save = useCallback(() => const save = useCallback(() =>
{ {
if(selectionMode === 'furni')
{
setSelectionMode('antenna');
applySelection(antennaIds);
}
const antennaSource = (antennaIds && antennaIds.length) ? antennaIds[0] : 0;
setIntParams([ setIntParams([
antennaSource, antennaSource,
furniForward, furniSource,
userForward, userSource,
signalPerFurni ? 1 : 0, signalPerFurni ? 1 : 0,
signalPerUser ? 1 : 0, signalPerUser ? 1 : 0,
0,
]); ]);
}, [ antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, setIntParams ]);
setStringParam(serializeForwardIds(forwardFurniIds));
}, [ selectionMode, antennaIds, furniSource, userSource, signalPerFurni, signalPerUser, forwardFurniIds, setIntParams, setStringParam, applySelection, setSelectionMode ]);
const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
const forwardSelectionEnabled = (furniSource === SOURCE_SELECTED);
return ( return (
<WiredActionBaseView <WiredActionBaseView
hasSpecialInput={ true } hasSpecialInput={ true }
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID }
cardStyle={ { width: '400px' } } cardStyle={ { width: '400px' } }
save={ save }> save={ save }
<div className="flex flex-col gap-2"> footer={ (
<WiredSourcesSelector
<div showFurni={ true }
className="cursor-pointer text-center" showUsers={ true }
onClick={ () => setShowAdvanced(!showAdvanced) }> furniSource={ furniSource }
<Text small underline> userSource={ userSource }
{ showAdvanced onChangeFurni={ onChangeFurniSource }
? LocalizeText('wiredfurni.params.hide_advanced') onChangeUsers={ setUserSource } />
: LocalizeText('wiredfurni.params.show_advanced') } ) }>
</Text> <div className="flex flex-col gap-3">
<div className="flex flex-col gap-1">
<Text bold>Antenne selezionate</Text>
<div className="flex items-center gap-2">
<Button
variant={ (selectionMode === 'antenna') ? 'primary' : 'secondary' }
onClick={ () => switchSelection('antenna') }>
Antenne
</Button>
<Text small>{ selectionLimit ? `${ antennaIds.length }/${ selectionLimit }` : antennaIds.length }</Text>
</div>
</div>
<div className="flex flex-col gap-1">
<Text bold>Furni selezionati</Text>
<div className="flex items-center gap-2">
<Button
variant={ (selectionMode === 'furni') ? 'primary' : 'secondary' }
disabled={ !forwardSelectionEnabled }
onClick={ () => switchSelection('furni') }>
Furni
</Button>
<Text small>{ selectionLimit ? `${ forwardFurniIds.length }/${ selectionLimit }` : forwardFurniIds.length }</Text>
</div>
</div> </div>
{ showAdvanced && <> <Text bold>{ LocalizeText('wiredfurni.params.signal.options') }</Text>
<div className="form-check">
{ /* --- Antennas --- */ } <input
<Text bold>{ LocalizeText('wiredfurni.params.sources.furni.title.signal_antenna') }</Text> type="checkbox"
<div className="form-check form-switch"> className="form-check-input"
<input id="signal-per-furni"
type="checkbox" checked={ signalPerFurni }
className="form-check-input" onChange={ e => setSignalPerFurni(e.target.checked) } />
id="signal-antenna-toggle" <label className="form-check-label" htmlFor="signal-per-furni">
role="switch" <Text small>{ LocalizeText('wiredfurni.params.signal.split_furni') }</Text>
checked={ antennaSource === ANTENNA_PICKED } </label>
onChange={ () => setAntennaSource(antennaSource === ANTENNA_PICKED ? ANTENNA_TRIGGER : ANTENNA_PICKED) } /> </div>
<label className="form-check-label" htmlFor="signal-antenna-toggle"> <div className="form-check">
<Text small>{ LocalizeText('wiredfurni.params.sources.furni.100') }</Text> <input
</label> type="checkbox"
</div> className="form-check-input"
id="signal-per-user"
{ /* --- Furni to forward --- */ } checked={ signalPerUser }
<Text bold>{ LocalizeText('wiredfurni.params.sources.furni.title.signal_forward') }</Text> onChange={ e => setSignalPerUser(e.target.checked) } />
<div className="flex items-center gap-2"> <label className="form-check-label" htmlFor="signal-per-user">
<div className="form-check form-switch flex-1 mb-0"> <Text small>{ LocalizeText('wiredfurni.params.signal.split_users') }</Text>
<input </label>
type="checkbox" </div>
className="form-check-input"
id="signal-furni-toggle"
role="switch"
checked={ furniForward !== FORWARD_NONE }
onChange={ e => setFurniForward(e.target.checked ? FORWARD_TRIGGER : FORWARD_NONE) } />
<label className="form-check-label" htmlFor="signal-furni-toggle">
<Text small>{ LocalizeText('wiredfurni.params.sources.furni.200') }</Text>
</label>
</div>
{ furniForward !== FORWARD_NONE &&
<select
className="form-select form-select-sm"
style={ { width: 'auto', minWidth: '160px' } }
value={ furniForward }
onChange={ e => setFurniForward(parseInt(e.target.value)) }>
{ FURNI_FORWARD_OPTIONS.map(opt =>
<option key={ opt.value } value={ opt.value }>{ LocalizeText(opt.label) }</option>
) }
</select>
}
</div>
{ /* --- Users to forward --- */ }
<Text bold>{ LocalizeText('wiredfurni.params.sources.users.title.signal_forward') }</Text>
<div className="flex items-center gap-2">
<div className="form-check form-switch flex-1 mb-0">
<input
type="checkbox"
className="form-check-input"
id="signal-user-toggle"
role="switch"
checked={ userForward !== FORWARD_NONE }
onChange={ e => setUserForward(e.target.checked ? FORWARD_TRIGGER : FORWARD_NONE) } />
<label className="form-check-label" htmlFor="signal-user-toggle">
<Text small>{ LocalizeText('wiredfurni.params.sources.users.0') }</Text>
</label>
</div>
{ userForward !== FORWARD_NONE &&
<select
className="form-select form-select-sm"
style={ { width: 'auto', minWidth: '160px' } }
value={ userForward }
onChange={ e => setUserForward(parseInt(e.target.value)) }>
{ USER_FORWARD_OPTIONS.map(opt =>
<option key={ opt.value } value={ opt.value }>{ LocalizeText(opt.label) }</option>
) }
</select>
}
</div>
<Text bold>{ LocalizeText('wiredfurni.params.signal.options') }</Text>
<div className="form-check">
<input
type="checkbox"
className="form-check-input"
id="signal-per-furni"
checked={ signalPerFurni }
onChange={ e => setSignalPerFurni(e.target.checked) } />
<label className="form-check-label" htmlFor="signal-per-furni">
<Text small>{ LocalizeText('wiredfurni.params.signal.split_furni') }</Text>
</label>
</div>
<div className="form-check">
<input
type="checkbox"
className="form-check-input"
id="signal-per-user"
checked={ signalPerUser }
onChange={ e => setSignalPerUser(e.target.checked) } />
<label className="form-check-label" htmlFor="signal-per-user">
<Text small>{ LocalizeText('wiredfurni.params.signal.split_users') }</Text>
</label>
</div>
</> }
</div> </div>
</WiredActionBaseView> </WiredActionBaseView>
); );
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@@ -10,7 +10,7 @@ export const WiredActionSetFurniStateToView: FC<{}> = props =>
const [ stateFlag, setStateFlag ] = useState(0); const [ stateFlag, setStateFlag ] = useState(0);
const [ directionFlag, setDirectionFlag ] = useState(0); const [ directionFlag, setDirectionFlag ] = useState(0);
const [ positionFlag, setPositionFlag ] = useState(0); const [ positionFlag, setPositionFlag ] = useState(0);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 3) return trigger.intData[3]; if(trigger?.intData?.length > 3) return trigger.intData[3];
@@ -29,23 +29,9 @@ export const WiredActionSetFurniStateToView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredActionTeleportView: FC<{}> = props => export const WiredActionTeleportView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
@@ -31,25 +31,11 @@ export const WiredActionTeleportView: FC<{}> = props =>
else setUserSource(0); else setUserSource(0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource, userSource ]); const save = () => setIntParams([ furniSource, userSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
export const WiredActionToggleFurniStateView: FC<{}> = props => export const WiredActionToggleFurniStateView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length >= 1) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0];
@@ -21,25 +21,11 @@ export const WiredActionToggleFurniStateView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource ]); const save = () => setIntParams([ furniSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredActionBaseView <WiredActionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredConditionBaseView } from './WiredConditionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredConditionActorIsOnFurniView: FC<{}> = props => export const WiredConditionActorIsOnFurniView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 0) return trigger.intData[0]; if(trigger?.intData?.length > 0) return trigger.intData[0];
@@ -29,25 +29,11 @@ export const WiredConditionActorIsOnFurniView: FC<{}> = props =>
else setUserSource(0); else setUserSource(0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource, userSource ]); const save = () => setIntParams([ furniSource, userSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredConditionBaseView <WiredConditionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredConditionBaseView } from './WiredConditionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredConditionFurniHasAvatarOnView: FC<{}> = props => export const WiredConditionFurniHasAvatarOnView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length >= 1) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0];
@@ -20,25 +20,11 @@ export const WiredConditionFurniHasAvatarOnView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource ]); const save = () => setIntParams([ furniSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredConditionBaseView <WiredConditionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredConditionBaseView } from './WiredConditionBaseView';
@@ -8,7 +8,7 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredConditionFurniHasFurniOnView: FC<{}> = props => export const WiredConditionFurniHasFurniOnView: FC<{}> = props =>
{ {
const [ requireAll, setRequireAll ] = useState(-1); const [ requireAll, setRequireAll ] = useState(-1);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 1) return trigger.intData[1]; if(trigger?.intData?.length > 1) return trigger.intData[1];
@@ -24,23 +24,9 @@ export const WiredConditionFurniHasFurniOnView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredConditionBaseView <WiredConditionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredConditionBaseView } from './WiredConditionBaseView';
@@ -8,7 +8,7 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredConditionFurniHasNotFurniOnView: FC<{}> = props => export const WiredConditionFurniHasNotFurniOnView: FC<{}> = props =>
{ {
const [ requireAll, setRequireAll ] = useState(-1); const [ requireAll, setRequireAll ] = useState(-1);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 1) return trigger.intData[1]; if(trigger?.intData?.length > 1) return trigger.intData[1];
@@ -24,23 +24,9 @@ export const WiredConditionFurniHasNotFurniOnView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredConditionBaseView <WiredConditionBaseView
@@ -1,12 +1,12 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredConditionBaseView } from './WiredConditionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredSourcesSelector } from '../WiredSourcesSelector';
export const WiredConditionFurniIsOfTypeView: FC<{}> = props => export const WiredConditionFurniIsOfTypeView: FC<{}> = props =>
{ {
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length >= 1) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0];
@@ -20,25 +20,11 @@ export const WiredConditionFurniIsOfTypeView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next);
};
const save = () => setIntParams([ furniSource ]); const save = () => setIntParams([ furniSource ]);
const requiresFurni = (furniSource === 100) const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE;
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredConditionBaseView <WiredConditionBaseView
@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredConditionBaseView } from './WiredConditionBaseView';
@@ -10,7 +10,7 @@ export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props =>
const [ stateFlag, setStateFlag ] = useState(0); const [ stateFlag, setStateFlag ] = useState(0);
const [ directionFlag, setDirectionFlag ] = useState(0); const [ directionFlag, setDirectionFlag ] = useState(0);
const [ positionFlag, setPositionFlag ] = useState(0); const [ positionFlag, setPositionFlag ] = useState(0);
const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null } = useWired(); const { trigger = null, setIntParams = null } = useWired();
const [ furniSource, setFurniSource ] = useState<number>(() => const [ furniSource, setFurniSource ] = useState<number>(() =>
{ {
if(trigger?.intData?.length > 3) return trigger.intData[3]; if(trigger?.intData?.length > 3) return trigger.intData[3];
@@ -28,23 +28,9 @@ export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props =>
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]); }, [ trigger ]);
const onChangeFurniSource = (next: number) => const onChangeFurniSource = (next: number) => setFurniSource(next);
{
if(furniIds.length && setFurniIds)
{
setFurniIds(prev =>
{
if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev);
return [];
});
}
setFurniSource(next); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
};
const requiresFurni = (furniSource === 100)
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
return ( return (
<WiredConditionBaseView <WiredConditionBaseView
@@ -1,35 +1,24 @@
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { LocalizeText, WiredFurniType } from '../../../../api'; import { LocalizeText, WiredFurniType } from '../../../../api';
import { Button, Text } from '../../../../common'; import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from '../actions/WiredActionBaseView'; import { WiredActionBaseView } from '../actions/WiredActionBaseView';
const SOURCE_FURNI_PICKED = 0; const SOURCE_FURNI_PICKED = 0;
const SOURCE_FURNI_SIGNAL = 1;
const SOURCE_FURNI_TRIGGER = 2;
const SOURCES = [
{ value: SOURCE_FURNI_PICKED, label: 'wiredfurni.params.sources.furni.100' },
{ value: SOURCE_FURNI_SIGNAL, label: 'wiredfurni.params.sources.furni.201' },
{ value: SOURCE_FURNI_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
];
export const WiredSelectorFurniByTypeView: FC<{}> = () => export const WiredSelectorFurniByTypeView: FC<{}> = () =>
{ {
const [ sourceType, setSourceType ] = useState(SOURCE_FURNI_PICKED);
const [ matchState, setMatchState ] = useState(false); const [ matchState, setMatchState ] = useState(false);
const [ filterExisting, setFilterExisting ] = useState(false); const [ filterExisting, setFilterExisting ] = useState(false);
const [ invert, setInvert ] = useState(false); const [ invert, setInvert ] = useState(false);
const { trigger = null, furniIds = [], setIntParams, setSelectByType, setInvertSelection } = useWired(); const { trigger = null, setIntParams, setSelectByType } = useWired();
useEffect(() => useEffect(() =>
{ {
if(!trigger) return; if(!trigger) return;
const p = trigger.intData; const p = trigger.intData;
if(p.length >= 1) setSourceType(p[0]);
if(p.length >= 2) setMatchState(p[1] === 1); if(p.length >= 2) setMatchState(p[1] === 1);
if(p.length >= 3) setFilterExisting(p[2] === 1); if(p.length >= 3) setFilterExisting(p[2] === 1);
if(p.length >= 4) setInvert(p[3] === 1); if(p.length >= 4) setInvert(p[3] === 1);
@@ -37,38 +26,20 @@ export const WiredSelectorFurniByTypeView: FC<{}> = () =>
useEffect(() => useEffect(() =>
{ {
setSelectByType(sourceType === SOURCE_FURNI_PICKED); setSelectByType(true);
}, [ sourceType, setSelectByType ]); }, [ setSelectByType ]);
useEffect(() =>
{
setInvertSelection(invert);
}, [ invert, setInvertSelection ]);
const save = useCallback(() => const save = useCallback(() =>
{ {
setIntParams([ setIntParams([
sourceType, SOURCE_FURNI_PICKED,
matchState ? 1 : 0, matchState ? 1 : 0,
filterExisting ? 1 : 0, filterExisting ? 1 : 0,
invert ? 1 : 0, invert ? 1 : 0,
]); ]);
}, [ sourceType, matchState, filterExisting, invert, setIntParams ]); }, [ matchState, filterExisting, invert, setIntParams ]);
const sourceIndex = SOURCES.findIndex(s => s.value === sourceType); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID;
const prevSource = () =>
setSourceType(SOURCES[(sourceIndex - 1 + SOURCES.length) % SOURCES.length].value);
const nextSource = () =>
setSourceType(SOURCES[(sourceIndex + 1) % SOURCES.length].value);
const requiresFurni = sourceType === SOURCE_FURNI_PICKED
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
const pickedCount = furniIds.length;
const pickedLimit = trigger?.maximumItemSelectionCount ?? 20;
return ( return (
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }> <WiredActionBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
@@ -104,22 +75,6 @@ export const WiredSelectorFurniByTypeView: FC<{}> = () =>
onChange={ e => setInvert(e.target.checked) } /> onChange={ e => setInvert(e.target.checked) } />
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text> <Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
</label> </label>
<hr className="m-0 bg-dark" />
<Text bold>{ LocalizeText('wiredfurni.params.sources.furni.title') }</Text>
<div className="flex items-center gap-2">
<Button variant="primary" className="px-2 py-1" onClick={ prevSource }>
<FaChevronLeft />
</Button>
<div className="flex flex-1 items-center justify-center">
<Text small>{ LocalizeText(SOURCES[sourceIndex >= 0 ? sourceIndex : 0].label) }</Text>
</div>
<Button variant="primary" className="px-2 py-1" onClick={ nextSource }>
<FaChevronRight />
</Button>
</div>
</div> </div>
</WiredActionBaseView> </WiredActionBaseView>
); );
@@ -1,6 +1,6 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { FriendlyTime, LocalizeText, WiredFurniType } from '../../../../api'; import { FriendlyTime, LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Slider, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView'; import { WiredTriggerBaseView } from './WiredTriggerBaseView';
@@ -1,6 +1,6 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common'; import { Slider, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView'; import { WiredTriggerBaseView } from './WiredTriggerBaseView';
@@ -4,12 +4,14 @@ import { Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView'; import { WiredTriggerBaseView } from './WiredTriggerBaseView';
const ANTENNA_INTERACTION_TYPES = [ 'antenna' ];
export const WiredTriggerReceiveSignalView: FC<{}> = () => export const WiredTriggerReceiveSignalView: FC<{}> = () =>
{ {
const [ senderCount, setSenderCount ] = useState(0); const [ senderCount, setSenderCount ] = useState(0);
const [ maxSenders, setMaxSenders ] = useState(5); const [ maxSenders, setMaxSenders ] = useState(5);
const { trigger = null } = useWired(); const { trigger = null, setAllowedInteractionTypes } = useWired();
useEffect(() => useEffect(() =>
{ {
@@ -20,8 +22,15 @@ export const WiredTriggerReceiveSignalView: FC<{}> = () =>
if(p.length >= 3) setMaxSenders(p[2]); if(p.length >= 3) setMaxSenders(p[2]);
}, [ trigger ]); }, [ trigger ]);
useEffect(() =>
{
setAllowedInteractionTypes(ANTENNA_INTERACTION_TYPES);
return () => setAllowedInteractionTypes(null);
}, [ setAllowedInteractionTypes ]);
return ( return (
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ null }> <WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID } save={ null }>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Text small>{ LocalizeText('wiredfurni.params.signal.senders_connected') }</Text> <Text small>{ LocalizeText('wiredfurni.params.signal.senders_connected') }</Text>
<Text bold small>{ senderCount }/{ maxSenders }</Text> <Text bold small>{ senderCount }/{ maxSenders }</Text>
+77 -8
View File
@@ -14,9 +14,9 @@ const useWiredState = () =>
const [ actionDelay, setActionDelay ] = useState<number>(0); const [ actionDelay, setActionDelay ] = useState<number>(0);
const [ allowsFurni, setAllowsFurni ] = useState<number>(WiredFurniType.STUFF_SELECTION_OPTION_NONE); const [ allowsFurni, setAllowsFurni ] = useState<number>(WiredFurniType.STUFF_SELECTION_OPTION_NONE);
const [ selectByType, setSelectByType ] = useState<boolean>(false); const [ selectByType, setSelectByType ] = useState<boolean>(false);
const [ invertSelection, setInvertSelection ] = useState<boolean>(false);
const [ neighborhoodTiles, setNeighborhoodTiles ] = useState<{ x: number; y: number }[] | null>(null); const [ neighborhoodTiles, setNeighborhoodTiles ] = useState<{ x: number; y: number }[] | null>(null);
const [ neighborhoodInvert, setNeighborhoodInvert ] = useState<boolean>(false); const [ neighborhoodInvert, setNeighborhoodInvert ] = useState<boolean>(false);
const [ allowedInteractionTypes, setAllowedInteractionTypes ] = useState<string[] | null>(null);
const { showConfirm = null, simpleAlert = null } = useNotification(); const { showConfirm = null, simpleAlert = null } = useNotification();
const saveWired = () => const saveWired = () =>
@@ -60,6 +60,30 @@ const useWiredState = () =>
if(objectId <= 0) return; if(objectId <= 0) return;
const getInteractionTypeName = (furniData: any): string =>
{
if(!furniData) return null;
const rawValue = (furniData as any).interactionType
?? (furniData as any).interactionTypeName
?? (furniData as any).interactionTypeId;
if(rawValue === undefined || rawValue === null) return null;
if(typeof rawValue !== 'string') return null;
return rawValue.toLowerCase();
};
const isAllowedInteraction = (furniData: any): boolean =>
{
if(!allowedInteractionTypes || !allowedInteractionTypes.length) return true;
const interactionType = getInteractionTypeName(furniData);
if(!interactionType) return true;
return allowedInteractionTypes.some(type => (type && type.toLowerCase() === interactionType));
};
if(selectByType && category === RoomObjectCategory.FLOOR) if(selectByType && category === RoomObjectCategory.FLOOR)
{ {
const roomId = GetRoomSession().roomId; const roomId = GetRoomSession().roomId;
@@ -71,6 +95,21 @@ const useWiredState = () =>
const sourceFurniData = GetSessionDataManager().getFloorItemData(typeId); const sourceFurniData = GetSessionDataManager().getFloorItemData(typeId);
if(!sourceFurniData) return; if(!sourceFurniData) return;
if(!isAllowedInteraction(sourceFurniData))
{
setFurniIds(prevValue =>
{
if(!prevValue.includes(objectId)) return prevValue;
const remaining = prevValue.filter(id => id !== objectId);
WiredSelectionVisualizer.hide(objectId);
return remaining;
});
return;
}
const matchFurniLine = sourceFurniData.furniLine; const matchFurniLine = sourceFurniData.furniLine;
const matchName = sourceFurniData.name; const matchName = sourceFurniData.name;
@@ -84,6 +123,8 @@ const useWiredState = () =>
const fd = GetSessionDataManager().getFloorItemData(tId); const fd = GetSessionDataManager().getFloorItemData(tId);
if(!fd) return false; if(!fd) return false;
if(!isAllowedInteraction(fd)) return false;
const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine; const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine;
return furniLineMatch || fd.name === matchName; return furniLineMatch || fd.name === matchName;
}; };
@@ -102,10 +143,8 @@ const useWiredState = () =>
} }
// ── Select a new group ────────────────────────────────────── // ── Select a new group ──────────────────────────────────────
if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue);
const allFloorObjects = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR); const allFloorObjects = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR);
const newIds: number[] = []; const newIds = [ ...prevValue ];
const limit = trigger.maximumItemSelectionCount; const limit = trigger.maximumItemSelectionCount;
for(const obj of allFloorObjects) for(const obj of allFloorObjects)
@@ -116,14 +155,16 @@ const useWiredState = () =>
const tId = obj.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID); const tId = obj.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
const fd = GetSessionDataManager().getFloorItemData(tId); const fd = GetSessionDataManager().getFloorItemData(tId);
if(!fd) continue; if(!fd) continue;
if(!isAllowedInteraction(fd)) continue;
const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine; const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine;
const matches = furniLineMatch || fd.name === matchName; const matches = furniLineMatch || fd.name === matchName;
if(invertSelection ? !matches : matches) newIds.push(obj.id); if(matches && !newIds.includes(obj.id)) newIds.push(obj.id);
} }
WiredSelectionVisualizer.applySelectionShaderToFurni(newIds); const addedIds = newIds.filter(id => !prevValue.includes(id));
if(addedIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(addedIds);
return newIds; return newIds;
}); });
@@ -131,6 +172,34 @@ const useWiredState = () =>
return; return;
} }
if(category === RoomObjectCategory.FLOOR && allowedInteractionTypes && allowedInteractionTypes.length)
{
const roomId = GetRoomSession().roomId;
const clickedObject = GetRoomEngine().getRoomObject(roomId, objectId, RoomObjectCategory.FLOOR);
if(!clickedObject) return;
const typeId = clickedObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
const sourceFurniData = GetSessionDataManager().getFloorItemData(typeId);
if(!sourceFurniData) return;
if(!isAllowedInteraction(sourceFurniData))
{
setFurniIds(prevValue =>
{
if(!prevValue.includes(objectId)) return prevValue;
const remaining = prevValue.filter(id => id !== objectId);
WiredSelectionVisualizer.hide(objectId);
return remaining;
});
return;
}
}
setFurniIds(prevValue => setFurniIds(prevValue =>
{ {
const newFurniIds = [ ...prevValue ]; const newFurniIds = [ ...prevValue ];
@@ -217,13 +286,13 @@ const useWiredState = () =>
}); });
setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE); setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE);
setSelectByType(false); setSelectByType(false);
setInvertSelection(false);
setNeighborhoodTiles(null); setNeighborhoodTiles(null);
setNeighborhoodInvert(false); setNeighborhoodInvert(false);
setAllowedInteractionTypes(null);
}; };
}, [ trigger ]); }, [ trigger ]);
return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired, setSelectByType, setInvertSelection, setNeighborhoodTiles, setNeighborhoodInvert }; return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired, setSelectByType, setNeighborhoodTiles, setNeighborhoodInvert, setAllowedInteractionTypes };
}; };
export const useWired = () => useBetween(useWiredState); export const useWired = () => useBetween(useWiredState);