From a53d788daf5cebab9634b302114086e34822f77c Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 5 Mar 2026 10:52:40 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=95=20Added=20wired=20wf=5Fslc=5Ffurni?= =?UTF-8?q?=5Fbytype?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/wired/WiredActionLayoutCode.ts | 1 + .../views/actions/WiredActionBaseView.tsx | 9 +- .../views/actions/WiredActionLayoutView.tsx | 3 + .../WiredSelectorFurniByTypeView.tsx | 126 ++++++++++++++++++ src/hooks/wired/useWired.ts | 81 ++++++++++- 5 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx diff --git a/src/api/wired/WiredActionLayoutCode.ts b/src/api/wired/WiredActionLayoutCode.ts index 2095e49..10b9842 100644 --- a/src/api/wired/WiredActionLayoutCode.ts +++ b/src/api/wired/WiredActionLayoutCode.ts @@ -28,4 +28,5 @@ export class WiredActionLayoutCode public static BOT_TALK_DIRECT_TO_AVTR: number = 27; public static FURNI_AREA_SELECTOR: number = 28; public static FURNI_NEIGHBORHOOD_SELECTOR: number = 29; + public static FURNI_BYTYPE_SELECTOR: number = 30; } diff --git a/src/components/wired/views/actions/WiredActionBaseView.tsx b/src/components/wired/views/actions/WiredActionBaseView.tsx index bc647c5..4962caf 100644 --- a/src/components/wired/views/actions/WiredActionBaseView.tsx +++ b/src/components/wired/views/actions/WiredActionBaseView.tsx @@ -11,11 +11,12 @@ export interface WiredActionBaseViewProps requiresFurni: number; save: () => void; cardStyle?: CSSProperties; + hideDelay?: boolean; } export const WiredActionBaseView: FC> = props => { - const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null, cardStyle = undefined } = props; + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null, cardStyle = undefined, hideDelay = false } = props; const { trigger = null, actionDelay = 0, setActionDelay = null } = useWired(); useEffect(() => @@ -26,15 +27,15 @@ export const WiredActionBaseView: FC return ( { children } - { !!children &&
} -
+ { !hideDelay && !!children &&
} + { !hideDelay &&
{ LocalizeText('wiredfurni.params.delay', [ 'seconds' ], [ GetWiredTimeLocale(actionDelay) ]) } setActionDelay(event) } /> -
+
}
); }; diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx index a9bd67f..6b0a0a9 100644 --- a/src/components/wired/views/actions/WiredActionLayoutView.tsx +++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx @@ -2,6 +2,7 @@ import { WiredActionLayoutCode } from '../../../../api'; import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView'; import { WiredActionFurniAreaView } from '../selectors/WiredActionFurniAreaView'; import { WiredSelectorFurniNeighborhoodView } from '../selectors/WiredSelectorFurniNeighborhoodView'; +import { WiredSelectorFurniByTypeView } from '../selectors/WiredSelectorFurniByTypeView'; import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView'; import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView'; import { WiredActionBotMoveView } from './WiredActionBotMoveView'; @@ -85,6 +86,8 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.FURNI_NEIGHBORHOOD_SELECTOR: return ; + case WiredActionLayoutCode.FURNI_BYTYPE_SELECTOR: + return ; } return null; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx new file mode 100644 index 0000000..345ff43 --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx @@ -0,0 +1,126 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Button, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from '../actions/WiredActionBaseView'; + +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<{}> = () => +{ + const [ sourceType, setSourceType ] = useState(SOURCE_FURNI_PICKED); + const [ matchState, setMatchState ] = useState(false); + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + + const { trigger = null, furniIds = [], setIntParams, setSelectByType, setInvertSelection } = useWired(); + + useEffect(() => + { + if(!trigger) return; + + const p = trigger.intData; + if(p.length >= 1) setSourceType(p[0]); + if(p.length >= 2) setMatchState(p[1] === 1); + if(p.length >= 3) setFilterExisting(p[2] === 1); + if(p.length >= 4) setInvert(p[3] === 1); + }, [ trigger ]); + + useEffect(() => + { + setSelectByType(sourceType === SOURCE_FURNI_PICKED); + }, [ sourceType, setSelectByType ]); + + useEffect(() => + { + setInvertSelection(invert); + }, [ invert, setInvertSelection ]); + + const save = useCallback(() => + { + setIntParams([ + sourceType, + matchState ? 1 : 0, + filterExisting ? 1 : 0, + invert ? 1 : 0, + ]); + }, [ sourceType, matchState, filterExisting, invert, setIntParams ]); + + const sourceIndex = SOURCES.findIndex(s => s.value === sourceType); + + 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 ( + +
+ + + +
+ + { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + + +
+ + { LocalizeText('wiredfurni.params.sources.furni.title') } + +
+ +
+ { LocalizeText(SOURCES[sourceIndex >= 0 ? sourceIndex : 0].label) } +
+ +
+
+
+ ); +}; diff --git a/src/hooks/wired/useWired.ts b/src/hooks/wired/useWired.ts index dca79e0..f25235f 100644 --- a/src/hooks/wired/useWired.ts +++ b/src/hooks/wired/useWired.ts @@ -1,7 +1,7 @@ -import { ConditionDefinition, OpenMessageComposer, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredSaveSuccessEvent } from '@nitrots/nitro-renderer'; +import { ConditionDefinition, GetRoomEngine, GetSessionDataManager, OpenMessageComposer, RoomObjectCategory, RoomObjectVariable, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredSaveSuccessEvent } from '@nitrots/nitro-renderer'; import { useEffect, useState } from 'react'; import { useBetween } from 'use-between'; -import { IsOwnerOfFloorFurniture, LocalizeText, SendMessageComposer, WiredFurniType, WiredSelectionVisualizer } from '../../api'; +import { GetRoomSession, IsOwnerOfFloorFurniture, LocalizeText, SendMessageComposer, WiredFurniType, WiredSelectionVisualizer } from '../../api'; import { useMessageEvent } from '../events'; import { useNotification } from '../notification'; @@ -13,6 +13,8 @@ const useWiredState = () => const [ furniIds, setFurniIds ] = useState([]); const [ actionDelay, setActionDelay ] = useState(0); const [ allowsFurni, setAllowsFurni ] = useState(WiredFurniType.STUFF_SELECTION_OPTION_NONE); + const [ selectByType, setSelectByType ] = useState(false); + const [ invertSelection, setInvertSelection ] = useState(false); const { showConfirm = null } = useNotification(); const saveWired = () => @@ -56,6 +58,77 @@ const useWiredState = () => if(objectId <= 0) return; + if(selectByType && category === RoomObjectCategory.FLOOR) + { + const roomId = GetRoomSession().roomId; + const clickedObject = GetRoomEngine().getRoomObject(roomId, objectId, RoomObjectCategory.FLOOR); + + if(!clickedObject) return; + + const typeId = clickedObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const sourceFurniData = GetSessionDataManager().getFloorItemData(typeId); + + if(!sourceFurniData) return; + + const matchFurniLine = sourceFurniData.furniLine; + const matchName = sourceFurniData.name; + + const isSameGroup = (id: number): boolean => + { + const obj = GetRoomEngine().getRoomObject(roomId, id, RoomObjectCategory.FLOOR); + if(!obj) return false; + + const tId = obj.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const fd = GetSessionDataManager().getFloorItemData(tId); + if(!fd) return false; + + const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine; + return furniLineMatch || fd.name === matchName; + }; + + setFurniIds(prevValue => + { + // ── Click on already-selected furni: deselect the whole group ── + if(prevValue.includes(objectId)) + { + const toRemove = prevValue.filter(id => isSameGroup(id)); + const remaining = prevValue.filter(id => !toRemove.includes(id)); + + WiredSelectionVisualizer.clearSelectionShaderFromFurni(toRemove); + + return remaining; + } + + // ── Select a new group ────────────────────────────────────── + if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); + + const allFloorObjects = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR); + const newIds: number[] = []; + const limit = trigger.maximumItemSelectionCount; + + for(const obj of allFloorObjects) + { + if(newIds.length >= limit) break; + if(obj.id < 0) continue; + + const tId = obj.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + const fd = GetSessionDataManager().getFloorItemData(tId); + if(!fd) continue; + + const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine; + const matches = furniLineMatch || fd.name === matchName; + + if(invertSelection ? !matches : matches) newIds.push(obj.id); + } + + WiredSelectionVisualizer.applySelectionShaderToFurni(newIds); + + return newIds; + }); + + return; + } + setFurniIds(prevValue => { const newFurniIds = [ ...prevValue ]; @@ -131,10 +204,12 @@ const useWiredState = () => return []; }); setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE); + setSelectByType(false); + setInvertSelection(false); }; }, [ trigger ]); - return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired }; + return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired, setSelectByType, setInvertSelection }; }; export const useWired = () => useBetween(useWiredState);