import { ConditionDefinition, GetRoomEngine, GetSessionDataManager, OpenMessageComposer, RoomObjectCategory, RoomObjectVariable, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredSaveSuccessEvent, WiredValidationErrorEvent } from '@nitrots/nitro-renderer'; import { useEffect, useState } from 'react'; import { useBetween } from 'use-between'; import { GetRoomSession, IsOwnerOfFloorFurniture, LocalizeText, SendMessageComposer, WiredFurniType, WiredSelectionVisualizer } from '../../api'; import { useMessageEvent } from '../events'; import { useNotification } from '../notification'; import { useWiredTools } from '../wired-tools/useWiredTools'; const useWiredState = () => { const [ trigger, setTrigger ] = useState(null); const [ intParams, setIntParams ] = useState([]); const [ stringParam, setStringParam ] = useState(''); const [ furniIds, setFurniIds ] = useState([]); const [ actionDelay, setActionDelay ] = useState(0); const [ allowsFurni, setAllowsFurni ] = useState(WiredFurniType.STUFF_SELECTION_OPTION_NONE); const selectByType = false; const [ neighborhoodTiles, setNeighborhoodTiles ] = useState<{ x: number; y: number }[] | null>(null); const [ neighborhoodInvert, setNeighborhoodInvert ] = useState(false); const [ allowedInteractionTypes, setAllowedInteractionTypes ] = useState(null); const [ allowedInteractionErrorKey, setAllowedInteractionErrorKey ] = useState(null); const { showConfirm = null, simpleAlert = null } = useNotification(); const { requestUserVariables = null, roomSettings = null } = useWiredTools(); const saveWired = () => { const save = (trigger: Triggerable) => { if(!trigger) return; if(trigger instanceof WiredActionDefinition) { SendMessageComposer(new UpdateActionMessageComposer(trigger.id, intParams, stringParam, furniIds, actionDelay, trigger.stuffTypeSelectionCode)); } else if(trigger instanceof TriggerDefinition) { SendMessageComposer(new UpdateTriggerMessageComposer(trigger.id, intParams, stringParam, furniIds, trigger.stuffTypeSelectionCode)); } else if(trigger instanceof ConditionDefinition) { SendMessageComposer(new UpdateConditionMessageComposer(trigger.id, intParams, stringParam, furniIds, trigger.stuffTypeSelectionCode)); } }; if(!IsOwnerOfFloorFurniture(trigger.id)) { showConfirm(LocalizeText('wiredfurni.nonowner.change.confirm.body'), () => { save(trigger); }, null, null, null, LocalizeText('wiredfurni.nonowner.change.confirm.title')); } else { save(trigger); } }; const selectObjectForWired = (objectId: number, category: number) => { if(!trigger || !allowsFurni) return; if(objectId <= 0) return; const getInteractionTypeName = (furniData: any): string => { if(!furniData) return null; const rawValue = (furniData).interactionType ?? (furniData).interactionTypeName ?? (furniData).interactionTypeId; if(rawValue === undefined || rawValue === null) return null; if(typeof rawValue !== 'string') return null; return rawValue.toLowerCase(); }; const getComparableInteractionNames = (furniData: any): string[] => { if(!furniData) return []; const values = [ getInteractionTypeName(furniData), (typeof (furniData).className === 'string') ? (furniData).className.toLowerCase() : null, (typeof (furniData).fullName === 'string') ? (furniData).fullName.toLowerCase() : null, (typeof (furniData).name === 'string') ? (furniData).name.toLowerCase() : null ]; return values.filter((value, index, array): value is string => !!value && (array.indexOf(value) === index)); }; const matchesAllowedPattern = (value: string, pattern: string) => { const normalizedPattern = pattern.toLowerCase(); if(normalizedPattern.endsWith('*')) return value.startsWith(normalizedPattern.slice(0, -1)); return (normalizedPattern === value); }; const isAllowedInteraction = (furniData: any): boolean => { if(!allowedInteractionTypes || !allowedInteractionTypes.length) return true; const comparableNames = getComparableInteractionNames(furniData); if(!comparableNames.length) return true; return comparableNames.some(value => allowedInteractionTypes.some(type => !!type && matchesAllowedPattern(value, type))); }; const handleDisallowedInteraction = () => { if(!allowedInteractionErrorKey) return; const message = (/^[a-z0-9_.]+$/i.test(allowedInteractionErrorKey)) ? LocalizeText(allowedInteractionErrorKey) : allowedInteractionErrorKey; simpleAlert(message, null, null, null, LocalizeText('wiredfurni.title')); }; 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; if(!isAllowedInteraction(sourceFurniData)) { handleDisallowedInteraction(); 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 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; if(!isAllowedInteraction(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 ────────────────────────────────────── const allFloorObjects = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR); const newIds = [ ...prevValue ]; 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; if(!isAllowedInteraction(fd)) continue; const furniLineMatch = matchFurniLine && matchFurniLine.length > 0 && fd.furniLine === matchFurniLine; const matches = furniLineMatch || fd.name === matchName; if(matches && !newIds.includes(obj.id)) newIds.push(obj.id); } const addedIds = newIds.filter(id => !prevValue.includes(id)); if(addedIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(addedIds); return newIds; }); 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(RoomObjectVariable.FURNITURE_TYPE_ID); const sourceFurniData = GetSessionDataManager().getFloorItemData(typeId); if(!sourceFurniData) return; if(!isAllowedInteraction(sourceFurniData)) { handleDisallowedInteraction(); setFurniIds(prevValue => { if(!prevValue.includes(objectId)) return prevValue; const remaining = prevValue.filter(id => id !== objectId); WiredSelectionVisualizer.hide(objectId); return remaining; }); return; } } setFurniIds(prevValue => { const newFurniIds = [ ...prevValue ]; const index = prevValue.indexOf(objectId); if(index >= 0) { newFurniIds.splice(index, 1); WiredSelectionVisualizer.hide(objectId); } else if(newFurniIds.length < trigger.maximumItemSelectionCount) { newFurniIds.push(objectId); WiredSelectionVisualizer.show(objectId); } return newFurniIds; }); }; useMessageEvent(WiredOpenEvent, event => { const parser = event.getParser(); SendMessageComposer(new OpenMessageComposer(parser.stuffId)); }); useMessageEvent(WiredSaveSuccessEvent, event => { const parser = event.getParser(); WiredSelectionVisualizer.clearAllSelectionShaders(); if(roomSettings?.canInspect && requestUserVariables) requestUserVariables(); setTrigger(null); }); useMessageEvent(WiredValidationErrorEvent, event => { const parser = event.getParser(); if(parser.info && parser.info.length) { const message = (/^[a-z0-9_.]+$/i.test(parser.info) ? LocalizeText(parser.info) : parser.info); simpleAlert(message, null, null, null, LocalizeText('wiredfurni.title')); } }); useMessageEvent(WiredFurniActionEvent, event => { const parser = event.getParser(); setTrigger(parser.definition); }); useMessageEvent(WiredFurniConditionEvent, event => { const parser = event.getParser(); setTrigger(parser.definition); }); useMessageEvent(WiredFurniTriggerEvent, event => { const parser = event.getParser(); setTrigger(parser.definition); }); useEffect(() => { if(!trigger) return; return () => { WiredSelectionVisualizer.clearAllSelectionShaders(); setIntParams([]); setStringParam(''); setActionDelay(0); setFurniIds(prevValue => { if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); return []; }); setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE); setNeighborhoodTiles(null); setNeighborhoodInvert(false); setAllowedInteractionTypes(null); setAllowedInteractionErrorKey(null); }; }, [ trigger ]); return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired, setNeighborhoodTiles, setNeighborhoodInvert, setAllowedInteractionTypes, setAllowedInteractionErrorKey }; }; export const useWired = () => useBetween(useWiredState);