diff --git a/public/UITexts.example b/public/UITexts.example index 980458f..fe20439 100644 --- a/public/UITexts.example +++ b/public/UITexts.example @@ -20,65 +20,5 @@ "widget.room.chat.clear_history": "leeg geschiedenis", "widget.room.youtube.shared": "YouTube word gedeeld", "widget.room.youtube.open_video": "Open de video", - "wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%", - "wiredfurni.params.sources.collapse": "Nascondi le impostazioni avanzate", - "wiredfurni.params.sources.expand": "Mostra le impostazioni avanzate", - "wiredfurni.params.quantifier_selection": "Abbina condizione se:", - "wiredfurni.params.quantifier.users.0": "Tutti gli utenti corrispondono", - "wiredfurni.params.quantifier.users.1": "Uno qualsiasi degli utenti corrisponde", - "wiredfurni.params.quantifier.users.neg.0": "Uno qualsiasi degli utenti non corrisponde", - "wiredfurni.params.quantifier.users.neg.1": "Nessuno degli utenti corrisponde", - "wiredfurni.params.quantifier.furni.0": "Tutti i Furni corrispondono", - "wiredfurni.params.quantifier.furni.1": "Uno qualsiasi dei Furni corrisponde", - "wiredfurni.params.usertype.1": "Habbo", - "wiredfurni.params.usertype.2": "Cucciolo", - "wiredfurni.params.usertype.4": "Bot", - "wiredfurni.params.sources.users.title.match.0": "Gli utenti da abbinare:", - "wiredfurni.params.sources.users.title.match.1": "Utenti da comparare con:", - "wiredfurni.params.sources.users.101": "Usa l'utente specificato dal nome", - "wiredfurni.params.comparison.0": "Più basso di", - "wiredfurni.params.comparison.1": "È uguale a", - "wiredfurni.params.comparison.2": "Più alto di", - "wiredfurni.params.team": "Scegli una squadra", - "wiredfurni.params.team.1": "Rossa", - "wiredfurni.params.team.2": "Verde", - "wiredfurni.params.team.3": "Blu", - "wiredfurni.params.team.4": "Gialla", - "wiredfurni.params.team.triggerer": "Squadra dell'innescatore", - "wiredfurni.params.comparison_selection": "Scegli tipo:", - "wiredfurni.params.setscore2": "La squadra deve segnare:", - "wiredfurni.params.placement_selection": "Posizione:", - "wiredfurni.params.placement.1": "1.", - "wiredfurni.params.placement.2": "2.", - "wiredfurni.params.placement.3": "3.", - "wiredfurni.params.placement.4": "4.", - "wiredfurni.params.time.hour_selection": "Ore:", - "wiredfurni.params.time.minute_selection": "Minuti:", - "wiredfurni.params.time.second_selection": "Secondi:", - "wiredfurni.params.time.skip": "Non usare il filtro", - "wiredfurni.params.time.exact": "Esatto", - "wiredfurni.params.time.range": "Range", - "wiredfurni.params.time.weekday_selection": "Giorno della settimana:", - "wiredfurni.params.time.weekday.1": "Lunedì", - "wiredfurni.params.time.weekday.2": "Martedì", - "wiredfurni.params.time.weekday.3": "Mercoledì", - "wiredfurni.params.time.weekday.4": "Giovedì", - "wiredfurni.params.time.weekday.5": "Venerdì", - "wiredfurni.params.time.weekday.6": "Sabato", - "wiredfurni.params.time.weekday.7": "Domenica", - "wiredfurni.params.time.day_selection": "Giorno:", - "wiredfurni.params.time.month_selection": "Mese:", - "wiredfurni.params.time.month.10": "Ott.", - "wiredfurni.params.time.month.11": "Nov.", - "wiredfurni.params.time.month.12": "Dic.", - "wiredfurni.params.time.month.1": "Gen.", - "wiredfurni.params.time.month.2": "Feb.", - "wiredfurni.params.time.month.3": "Mar.", - "wiredfurni.params.time.month.4": "Apr.", - "wiredfurni.params.time.month.5": "Mag.", - "wiredfurni.params.time.month.6": "Giu.", - "wiredfurni.params.time.month.7": "Lug.", - "wiredfurni.params.time.month.8": "Ago.", - "wiredfurni.params.time.month.9": "Set.", - "wiredfurni.params.time.year_selection": "Anno:" + "wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%" } diff --git a/src/api/wired/WiredActionLayoutCode.ts b/src/api/wired/WiredActionLayoutCode.ts index c9d4324..94c8f94 100644 --- a/src/api/wired/WiredActionLayoutCode.ts +++ b/src/api/wired/WiredActionLayoutCode.ts @@ -37,4 +37,23 @@ export class WiredActionLayoutCode public static FURNI_TO_USER: number = 36; public static USER_TO_FURNI: number = 37; public static FURNI_TO_FURNI: number = 38; + public static SET_ALTITUDE: number = 39; + public static RELATIVE_MOVE: number = 40; + public static CONTROL_CLOCK: number = 41; + public static ADJUST_CLOCK: number = 42; + public static MOVE_ROTATE_USER: number = 43; + public static FURNI_ALTITUDE_SELECTOR: number = 44; + public static FURNI_ON_FURNI_SELECTOR: number = 45; + public static FURNI_PICKS_SELECTOR: number = 46; + public static FURNI_SIGNAL_SELECTOR: number = 47; + public static USERS_SIGNAL_SELECTOR: number = 48; + public static USERS_BY_TYPE_SELECTOR: number = 49; + public static USERS_TEAM_SELECTOR: number = 50; + public static USERS_BY_ACTION_SELECTOR: number = 51; + public static USERS_BY_NAME_SELECTOR: number = 52; + public static USERS_ON_FURNI_SELECTOR: number = 53; + public static USERS_GROUP_SELECTOR: number = 54; + public static USERS_HANDITEM_SELECTOR: number = 55; + public static FILTER_FURNI_EXTRA: number = 56; + public static FILTER_USER_EXTRA: number = 57; } diff --git a/src/api/wired/WiredConditionLayoutCode.ts b/src/api/wired/WiredConditionLayoutCode.ts index b344f8b..f6fd56a 100644 --- a/src/api/wired/WiredConditionLayoutCode.ts +++ b/src/api/wired/WiredConditionLayoutCode.ts @@ -26,6 +26,7 @@ export class WiredConditionlayout public static NOT_ACTOR_WEARING_EFFECT: number = 23; public static DATE_RANGE_ACTIVE: number = 24; public static ACTOR_HAS_HANDITEM: number = 25; + public static MOVEMENT_VALIDATION: number = 26; public static COUNTER_TIME_MATCHES: number = 27; public static USER_PERFORMS_ACTION: number = 28; public static HAS_ALTITUDE: number = 29; @@ -37,4 +38,6 @@ export class WiredConditionlayout public static TEAM_HAS_RANK: number = 35; public static MATCH_TIME: number = 36; public static MATCH_DATE: number = 37; + public static ACTOR_DIR: number = 38; + public static SLC_QUANTITY: number = 39; } diff --git a/src/api/wired/WiredTriggerLayoutCode.ts b/src/api/wired/WiredTriggerLayoutCode.ts index ac80e07..6f6fbe8 100644 --- a/src/api/wired/WiredTriggerLayoutCode.ts +++ b/src/api/wired/WiredTriggerLayoutCode.ts @@ -21,4 +21,5 @@ export class WiredTriggerLayout public static CLICK_TILE: number = 19; public static CLICK_USER: number = 20; public static USER_PERFORMS_ACTION: number = 21; + public static CLOCK_COUNTER: number = 22; } diff --git a/src/assets/images/wired/icon_wired_dir_e.png b/src/assets/images/wired/icon_wired_dir_e.png new file mode 100644 index 0000000..dfcfff2 Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_e.png differ diff --git a/src/assets/images/wired/icon_wired_dir_horizontal_random.png b/src/assets/images/wired/icon_wired_dir_horizontal_random.png new file mode 100644 index 0000000..63c1b4f Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_horizontal_random.png differ diff --git a/src/assets/images/wired/icon_wired_dir_n.png b/src/assets/images/wired/icon_wired_dir_n.png new file mode 100644 index 0000000..7d8aa0e Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_n.png differ diff --git a/src/assets/images/wired/icon_wired_dir_ne.png b/src/assets/images/wired/icon_wired_dir_ne.png new file mode 100644 index 0000000..e98b65c Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_ne.png differ diff --git a/src/assets/images/wired/icon_wired_dir_nw.png b/src/assets/images/wired/icon_wired_dir_nw.png new file mode 100644 index 0000000..5a04aea Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_nw.png differ diff --git a/src/assets/images/wired/icon_wired_dir_random.png b/src/assets/images/wired/icon_wired_dir_random.png new file mode 100644 index 0000000..2fcfd65 Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_random.png differ diff --git a/src/assets/images/wired/icon_wired_dir_s.png b/src/assets/images/wired/icon_wired_dir_s.png new file mode 100644 index 0000000..5b0e4e7 Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_s.png differ diff --git a/src/assets/images/wired/icon_wired_dir_se.png b/src/assets/images/wired/icon_wired_dir_se.png new file mode 100644 index 0000000..3bab968 Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_se.png differ diff --git a/src/assets/images/wired/icon_wired_dir_sw.png b/src/assets/images/wired/icon_wired_dir_sw.png new file mode 100644 index 0000000..9075fb9 Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_sw.png differ diff --git a/src/assets/images/wired/icon_wired_dir_vertical_random.png b/src/assets/images/wired/icon_wired_dir_vertical_random.png new file mode 100644 index 0000000..e9b8993 Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_vertical_random.png differ diff --git a/src/assets/images/wired/icon_wired_dir_w.png b/src/assets/images/wired/icon_wired_dir_w.png new file mode 100644 index 0000000..26b787a Binary files /dev/null and b/src/assets/images/wired/icon_wired_dir_w.png differ diff --git a/src/common/card/NitroCardView.tsx b/src/common/card/NitroCardView.tsx index 2735f61..8684ccd 100644 --- a/src/common/card/NitroCardView.tsx +++ b/src/common/card/NitroCardView.tsx @@ -6,16 +6,17 @@ import { NitroCardContextProvider } from './NitroCardContext'; export interface NitroCardViewProps extends DraggableWindowProps, ColumnProps { theme?: string; + isResizable?: boolean; } export const NitroCardView: FC = props => { - const { theme = 'primary', uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, overflow = 'hidden', position = 'relative', gap = 0, classNames = [], ...rest } = props; + const { theme = 'primary', uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, overflow = 'hidden', position = 'relative', gap = 0, classNames = [], isResizable = true, ...rest } = props; const elementRef = useRef(); const getClassNames = useMemo(() => { - const newClassNames: string[] = [ 'resize', 'rounded', 'shadow', ]; + const newClassNames: string[] = [ isResizable ? 'resize' : 'resize-none', 'rounded', 'shadow' ]; // Card Theme Changer newClassNames.push('border border-[#283F5D]'); @@ -23,7 +24,7 @@ export const NitroCardView: FC = props => if(classNames.length) newClassNames.push(...classNames); return newClassNames; - }, [ classNames ]); + }, [ classNames, isResizable ]); return ( diff --git a/src/common/draggable-window/DraggableWindow.tsx b/src/common/draggable-window/DraggableWindow.tsx index 7400a5f..9bb7e34 100644 --- a/src/common/draggable-window/DraggableWindow.tsx +++ b/src/common/draggable-window/DraggableWindow.tsx @@ -232,7 +232,9 @@ export const DraggableWindow: FC = props => { ...dragStyle, left: 0, top: 0, - transform: `translate(${offset.x + delta.x}px, ${offset.y + delta.y}px)`, + transform: `translate3d(${offset.x + delta.x}px, ${offset.y + delta.y}px, 0)`, + willChange: isDragging ? 'transform' : undefined, + backfaceVisibility: 'hidden', visibility: isPositioned ? 'visible' : 'hidden' }} onMouseDownCapture={onMouseDown} diff --git a/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx b/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx index c12ed78..fd81f96 100644 --- a/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx +++ b/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx @@ -1,5 +1,5 @@ -import { GetSessionDataManager, RoomEngineEvent, RoomEnterEffect, RoomSessionDanceEvent } from '@nitrots/nitro-renderer'; -import { FC, useState } from 'react'; +import { AddLinkEventTracker, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker, RoomEngineEvent, RoomEnterEffect, RoomSessionDanceEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; import { AvatarInfoFurni, AvatarInfoPet, AvatarInfoRentableBot, AvatarInfoUser, GetConfigurationValue, RoomWidgetUpdateRentableBotChatEvent } from '../../../../api'; import { Column } from '../../../../common'; import { useAvatarInfoWidget, useNitroEvent, useRoom, useUiEvent } from '../../../../hooks'; @@ -23,12 +23,29 @@ import { AvatarInfoWidgetRentableBotView } from './menu/AvatarInfoWidgetRentable export const AvatarInfoWidgetView: FC<{}> = props => { + const BLOCK_MENU_WINDOW_MS = 500; + const BLOCK_ROTATE_WINDOW_MS = 500; const [ isGameMode, setGameMode ] = useState(false); const [ isDancing, setIsDancing ] = useState(false); const [ rentableBotChatEvent, setRentableBotChatEvent ] = useState(null); const { avatarInfo = null, setAvatarInfo = null, activeNameBubble = null, setActiveNameBubble = null, nameBubbles = [], removeNameBubble = null, productBubbles = [], confirmingProduct = null, updateConfirmingProduct = null, removeProductBubble = null, isDecorating = false, setIsDecorating = null } = useAvatarInfoWidget(); const { roomSession = null } = useRoom(); + const updateAvatarClickControl = (updates: { suppressMenuUntil?: number; suppressRotateUntil?: number; }) => + { + const globalScope = (globalThis as any); + + if(!globalScope.__nitroAvatarClickControl) + { + globalScope.__nitroAvatarClickControl = { + suppressMenuUntil: 0, + suppressRotateUntil: 0 + }; + } + + Object.assign(globalScope.__nitroAvatarClickControl, updates); + }; + useNitroEvent(RoomEngineEvent.NORMAL_MODE, event => { if(isGameMode) setGameMode(false); @@ -48,6 +65,41 @@ export const AvatarInfoWidgetView: FC<{}> = props => useUiEvent(RoomWidgetUpdateRentableBotChatEvent.UPDATE_CHAT, event => setRentableBotChatEvent(event)); + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'hide': + setAvatarInfo(null); + setActiveNameBubble(null); + + if(roomSession) GetRoomEngine().clearSelectedAvatar(roomSession.roomId); + return; + case 'block-menu': + updateAvatarClickControl({ suppressMenuUntil: Date.now() + BLOCK_MENU_WINDOW_MS }); + setAvatarInfo(null); + setActiveNameBubble(null); + return; + case 'block-rotate': + updateAvatarClickControl({ suppressRotateUntil: Date.now() + BLOCK_ROTATE_WINDOW_MS }); + return; + } + }, + eventUrlPrefix: 'avatar-info/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ roomSession, setActiveNameBubble, setAvatarInfo ]); + const getMenuView = () => { if(!roomSession || isGameMode) return null; diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx index 1791979..12aca46 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx @@ -61,6 +61,8 @@ export const InfoStandWidgetUserView: FC = props = if (oldBadges === event.badges.join('')) return; setAvatarInfo(prevValue => { + if (!prevValue) return prevValue; + const newValue = CloneObject(prevValue); newValue.badges = event.badges; return newValue; @@ -71,6 +73,8 @@ export const InfoStandWidgetUserView: FC = props = if (!avatarInfo || avatarInfo.roomIndex !== event.roomIndex) return; setAvatarInfo(prevValue => { + if (!prevValue) return prevValue; + const newValue = CloneObject(prevValue); newValue.figure = event.figure; newValue.motto = event.customInfo; @@ -86,6 +90,8 @@ export const InfoStandWidgetUserView: FC = props = if (!avatarInfo || avatarInfo.roomIndex !== event.roomIndex) return; setAvatarInfo(prevValue => { + if (!prevValue) return prevValue; + const newValue = CloneObject(prevValue); const clearGroup = (event.status === -1) || (event.habboGroupId <= 0); @@ -272,4 +278,4 @@ export const InfoStandWidgetUserView: FC = props = )} ); -}; \ No newline at end of file +}; diff --git a/src/components/wired/views/WiredBaseView.tsx b/src/components/wired/views/WiredBaseView.tsx index f9477af..96f302a 100644 --- a/src/components/wired/views/WiredBaseView.tsx +++ b/src/components/wired/views/WiredBaseView.tsx @@ -1,4 +1,4 @@ -import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { GetRoomEngine, GetSessionDataManager } from '@nitrots/nitro-renderer'; import { CSSProperties, FC, PropsWithChildren, ReactNode, useEffect, useState } from 'react'; import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../api'; import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; @@ -24,8 +24,15 @@ export const WiredBaseView: FC> = props => const [ needsSave, setNeedsSave ] = useState(false); const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired(); + const clearRoomAreaSelection = () => + { + GetRoomEngine().areaSelectionManager.clearHighlight(); + GetRoomEngine().areaSelectionManager.deactivate(); + }; + const onClose = () => { + clearRoomAreaSelection(); WiredSelectionVisualizer.clearAllSelectionShaders(); setTrigger(null); }; @@ -92,6 +99,11 @@ export const WiredBaseView: FC> = props => } }, [ trigger, hasSpecialInput, setIntParams, setStringParam, setFurniIds ]); + useEffect(() => + { + return () => clearRoomAreaSelection(); + }, []); + useEffect(() => { if(!trigger) return; @@ -99,8 +111,16 @@ export const WiredBaseView: FC> = props => setAllowsFurni(requiresFurni); }, [ trigger, requiresFurni, setAllowsFurni ]); + const resolvedCardStyle: CSSProperties = { ...cardStyle }; + + if(resolvedCardStyle.width !== undefined) + { + resolvedCardStyle.minWidth = resolvedCardStyle.width; + delete resolvedCardStyle.width; + } + return ( - +
diff --git a/src/components/wired/views/WiredDirectionIcon.tsx b/src/components/wired/views/WiredDirectionIcon.tsx new file mode 100644 index 0000000..8179ab8 --- /dev/null +++ b/src/components/wired/views/WiredDirectionIcon.tsx @@ -0,0 +1,53 @@ +import { FC } from 'react'; +import iconWiredDirE from '../../../assets/images/wired/icon_wired_dir_e.png'; +import iconWiredDirN from '../../../assets/images/wired/icon_wired_dir_n.png'; +import iconWiredDirNe from '../../../assets/images/wired/icon_wired_dir_ne.png'; +import iconWiredDirNw from '../../../assets/images/wired/icon_wired_dir_nw.png'; +import iconWiredDirS from '../../../assets/images/wired/icon_wired_dir_s.png'; +import iconWiredDirSe from '../../../assets/images/wired/icon_wired_dir_se.png'; +import iconWiredDirSw from '../../../assets/images/wired/icon_wired_dir_sw.png'; +import iconWiredDirW from '../../../assets/images/wired/icon_wired_dir_w.png'; + +export const WIRED_DIRECTION_GRID = [ + [ 7, 0, 1, 2 ], + [ 6, 5, 4, 3 ] +]; + +interface WiredDirectionIconProps +{ + direction: number; + selected?: boolean; + debugValue?: number | string; + iconSrc?: string; + showImage?: boolean; +} + +const DIRECTION_ICON_MAP: Record = { + 0: iconWiredDirN, + 1: iconWiredDirNe, + 2: iconWiredDirE, + 3: iconWiredDirSe, + 4: iconWiredDirS, + 5: iconWiredDirSw, + 6: iconWiredDirW, + 7: iconWiredDirNw +}; + +export const WiredDirectionIcon: FC = props => +{ + const { direction = 0, selected = false, debugValue = null, iconSrc = null, showImage = true } = props; + const icon = iconSrc ?? DIRECTION_ICON_MAP[direction]; + + return ( + + { showImage && + } + { (debugValue !== null && debugValue !== undefined) && + { debugValue } } + + ); +}; diff --git a/src/components/wired/views/WiredHandItemField.tsx b/src/components/wired/views/WiredHandItemField.tsx new file mode 100644 index 0000000..b4b17ac --- /dev/null +++ b/src/components/wired/views/WiredHandItemField.tsx @@ -0,0 +1,59 @@ +import { RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { FC, useMemo } from 'react'; +import { GetOwnRoomObject, LocalizeText } from '../../../api'; +import { Button, Text } from '../../../common'; + +export const DEFAULT_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; + +interface WiredHandItemFieldProps +{ + handItemId: number; + onChange: (value: number) => void; + labelKey?: string; + showCopyButton?: boolean; +} + +export const WiredHandItemField: FC = props => +{ + const { handItemId = 0, onChange = null, labelKey = 'wiredfurni.params.handitem', showCopyButton = false } = props; + + const options = useMemo(() => + { + const values = [ ...DEFAULT_HAND_ITEM_IDS ]; + + if(handItemId > 0 && !values.includes(handItemId)) values.unshift(handItemId); + + return values; + }, [ handItemId ]); + + const getLabel = (value: number) => + { + const key = `handitem${ value }`; + const localized = LocalizeText(key); + + if(localized && localized !== key) return localized; + + return `${ value }`; + }; + + const copyOwnHandItem = () => + { + const roomObject = GetOwnRoomObject(); + const copiedHandItem = (roomObject?.model?.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + onChange && onChange(copiedHandItem); + }; + + return ( +
+ { LocalizeText(labelKey) } +
+ + { showCopyButton && } +
+
+ ); +}; diff --git a/src/components/wired/views/WiredSourcesSelector.tsx b/src/components/wired/views/WiredSourcesSelector.tsx index 2d5ebc1..ab1288e 100644 --- a/src/components/wired/views/WiredSourcesSelector.tsx +++ b/src/components/wired/views/WiredSourcesSelector.tsx @@ -16,6 +16,13 @@ export const USER_SOURCES = [ { value: 201, label: 'wiredfurni.params.sources.users.201' } ]; +export const BOT_SOURCES = [ + { value: 0, label: 'wiredfurni.params.sources.users.0' }, + { value: 100, label: 'wiredfurni.params.sources.users.100' }, + { value: 200, label: 'wiredfurni.params.sources.users.200' }, + { value: 201, label: 'wiredfurni.params.sources.users.201' } +]; + export interface WiredSourceOption { value: number; diff --git a/src/components/wired/views/actions/WiredActionAdjustClockView.tsx b/src/components/wired/views/actions/WiredActionAdjustClockView.tsx new file mode 100644 index 0000000..8ae55ec --- /dev/null +++ b/src/components/wired/views/actions/WiredActionAdjustClockView.tsx @@ -0,0 +1,123 @@ +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 COUNTER_INTERACTION_TYPES = [ 'game_upcounter' ]; +const MINUTES_MIN = 0; +const MINUTES_MAX = 99; +const HALF_SECONDS_MIN = 0; +const HALF_SECONDS_MAX = 119; + +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; +}; + +const normalizeMinutes = (value: number) => Math.max(MINUTES_MIN, Math.min(MINUTES_MAX, value)); +const normalizeHalfSeconds = (value: number) => Math.max(HALF_SECONDS_MIN, Math.min(HALF_SECONDS_MAX, value)); + +const formatSeconds = (halfSeconds: number) => +{ + const value = normalizeHalfSeconds(halfSeconds) / 2; + const text = value.toFixed(1); + + return text.endsWith('.0') ? text.slice(0, -2) : text; +}; + +export const WiredActionAdjustClockView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired(); + const [ operator, setOperator ] = useState(2); + const [ furniSource, setFurniSource ] = useState(() => + { + if(trigger?.intData?.length > 1) return trigger.intData[1]; + return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; + }); + const [ minutes, setMinutes ] = useState(0); + const [ halfSeconds, setHalfSeconds ] = useState(0); + + const secondsLabel = useMemo(() => formatSeconds(halfSeconds), [ halfSeconds ]); + + 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)); + setMinutes((trigger.intData.length > 2) ? normalizeMinutes(trigger.intData[2]) : 0); + setHalfSeconds((trigger.intData.length > 3) ? normalizeHalfSeconds(trigger.intData[3]) : 0); + }, [ trigger ]); + + useEffect(() => + { + setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES); + setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni'); + + return () => + { + setAllowedInteractionTypes(null); + setAllowedInteractionErrorKey(null); + }; + }, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]); + + const save = () => + { + setIntParams([ + operator, + furniSource, + normalizeMinutes(minutes), + normalizeHalfSeconds(halfSeconds) + ]); + }; + + return ( + }> +
+ { OPERATOR_OPTIONS.map(option => + { + return ( +
+ setOperator(option.value) } /> + { LocalizeText(option.label) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.time.minute_selection') } + setMinutes(normalizeMinutes(event as number)) } /> + { minutes } +
+
+ { LocalizeText('wiredfurni.params.time.second_selection') } + setHalfSeconds(normalizeHalfSeconds(event as number)) } /> + { secondsLabel } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx b/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx index e359037..eaf2037 100644 --- a/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx +++ b/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx @@ -5,31 +5,43 @@ import { Button, LayoutAvatarImageView, Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; +import { BOT_SOURCES, WiredSourcesSelector } from '../WiredSourcesSelector'; const DEFAULT_FIGURE: string = 'hd-180-1.ch-210-66.lg-270-82.sh-290-81'; +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCES.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotChangeFigureView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); const [ figure, setFigure ] = useState(''); - const { trigger = null, setStringParam = null } = useWired(); + const [ botSource, setBotSource ] = useState(100); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); - const save = () => setStringParam((botName + WIRED_STRING_DELIMETER + figure)); + const save = () => + { + setStringParam(((botSource === 100) ? botName : '') + WIRED_STRING_DELIMETER + figure); + setIntParams([ botSource ]); + }; useEffect(() => { const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + const nextBotName = (data.length > 0) ? data[0] : ''; - if(data.length > 0) setBotName(data[0]); + if(data.length > 0) setBotName(nextBotName); if(data.length > 1) setFigure(data[1].length > 0 ? data[1] : DEFAULT_FIGURE); + else setFigure(DEFAULT_FIGURE); + + setBotSource((trigger.intData.length > 0) ? normalizeBotSource(trigger.intData[0], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); return ( - -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> }> + { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
}
diff --git a/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx b/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx index e78ce8a..db00b7d 100644 --- a/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx +++ b/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx @@ -4,13 +4,16 @@ import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; -import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { BOT_SOURCES, WiredSourcesSelector } from '../WiredSourcesSelector'; + +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCES.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotFollowAvatarView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); const [ followMode, setFollowMode ] = useState(-1); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + const [ botSource, setBotSource ] = useState(100); const [ userSource, setUserSource ] = useState(() => { if(trigger?.intData?.length > 1) return trigger.intData[1]; @@ -19,15 +22,17 @@ export const WiredActionBotFollowAvatarView: FC<{}> = props => const save = () => { - setStringParam(botName); - setIntParams([ followMode, userSource ]); + setStringParam((botSource === 100) ? botName : ''); + setIntParams([ followMode, userSource, botSource ]); }; useEffect(() => { - setBotName(trigger.stringData); + const nextBotName = trigger.stringData || ''; + setBotName(nextBotName); setFollowMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + setBotSource((trigger.intData.length > 2) ? normalizeBotSource(trigger.intData[2], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); return ( @@ -35,11 +40,18 @@ export const WiredActionBotFollowAvatarView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } - footer={ }> -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
+ footer={ +
+ +
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> +
+ }> + { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
}
setFollowMode(1) } /> diff --git a/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx b/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx index 472e46f..4b25adb 100644 --- a/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx +++ b/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx @@ -4,32 +4,47 @@ import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; -import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredHandItemField } from '../WiredHandItemField'; -const ALLOWED_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; +const USER_SOURCE_OPTIONS: WiredSourceOption[] = [ + { value: 0, label: 'wiredfurni.params.sources.users.0' }, + { value: 200, label: 'wiredfurni.params.sources.users.200' }, + { value: 201, label: 'wiredfurni.params.sources.users.201' } +]; + +const BOT_SOURCE_OPTIONS: WiredSourceOption[] = [ + { value: 0, label: 'wiredfurni.params.sources.users.0' }, + { value: 100, label: 'wiredfurni.params.sources.users.100' }, + { value: 200, label: 'wiredfurni.params.sources.users.200' }, + { value: 201, label: 'wiredfurni.params.sources.users.201' } +]; + +const normalizeUserSource = (value: number) => (USER_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 0); +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotGiveHandItemView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); const [ handItemId, setHandItemId ] = useState(-1); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); - const [ userSource, setUserSource ] = useState(() => - { - if(trigger?.intData?.length > 1) return trigger.intData[1]; - return 0; - }); + const [ userSource, setUserSource ] = useState(0); + const [ botSource, setBotSource ] = useState(100); const save = () => { - setStringParam(botName); - setIntParams([ handItemId, userSource ]); + setStringParam((botSource === 100) ? botName : ''); + setIntParams([ handItemId, userSource, botSource ]); }; useEffect(() => { - setBotName(trigger.stringData); + const nextBotName = trigger.stringData || ''; + + setBotName(nextBotName); setHandItemId((trigger.intData.length > 0) ? trigger.intData[0] : 0); - setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + setUserSource((trigger.intData.length > 1) ? normalizeUserSource(trigger.intData[1]) : 0); + setBotSource((trigger.intData.length > 2) ? normalizeBotSource(trigger.intData[2], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); return ( @@ -37,18 +52,23 @@ export const WiredActionBotGiveHandItemView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } - footer={ }> -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
-
- { LocalizeText('wiredfurni.params.handitem') } - + footer={ +
+ +
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> +
+ }> +
+ setBotSource(event.target.checked ? 100 : 0) } /> +
+ { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
} + ); }; diff --git a/src/components/wired/views/actions/WiredActionBotMoveView.tsx b/src/components/wired/views/actions/WiredActionBotMoveView.tsx index d07bc7b..b80e92e 100644 --- a/src/components/wired/views/actions/WiredActionBotMoveView.tsx +++ b/src/components/wired/views/actions/WiredActionBotMoveView.tsx @@ -4,11 +4,14 @@ import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; -import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { BOT_SOURCES, WiredSourcesSelector } from '../WiredSourcesSelector'; + +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCES.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotMoveView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); + const [ botSource, setBotSource ] = useState(100); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); const [ furniSource, setFurniSource ] = useState(() => @@ -19,18 +22,21 @@ export const WiredActionBotMoveView: FC<{}> = props => const save = () => { - setStringParam(botName); - setIntParams([ furniSource ]); + setStringParam((botSource === 100) ? botName : ''); + setIntParams([ furniSource, botSource ]); }; useEffect(() => { if(!trigger) return; - setBotName(trigger.stringData); + const nextBotName = trigger.stringData || ''; + setBotName(nextBotName); if(trigger.intData.length >= 1) setFurniSource(trigger.intData[0]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); + + setBotSource((trigger.intData.length >= 2) ? normalizeBotSource(trigger.intData[1], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); @@ -42,11 +48,18 @@ export const WiredActionBotMoveView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } - footer={ }> -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
+ footer={ +
+ +
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> +
+ }> + { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
} ); }; diff --git a/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx b/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx index 4961154..5d7db10 100644 --- a/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx +++ b/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx @@ -4,13 +4,16 @@ import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; -import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { BOT_SOURCES, WiredSourcesSelector } from '../WiredSourcesSelector'; + +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCES.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotTalkToAvatarView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); const [ message, setMessage ] = useState(''); const [ talkMode, setTalkMode ] = useState(-1); + const [ botSource, setBotSource ] = useState(100); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -20,19 +23,21 @@ export const WiredActionBotTalkToAvatarView: FC<{}> = props => const save = () => { - setStringParam(botName + WIRED_STRING_DELIMETER + message); - setIntParams([ talkMode, userSource ]); + setStringParam(((botSource === 100) ? botName : '') + WIRED_STRING_DELIMETER + message); + setIntParams([ talkMode, userSource, botSource ]); }; useEffect(() => { const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + const nextBotName = (data.length > 0) ? data[0] : ''; - if(data.length > 0) setBotName(data[0]); + if(data.length > 0) setBotName(nextBotName); if(data.length > 1) setMessage(data[1].length > 0 ? data[1] : ''); setTalkMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + setBotSource((trigger.intData.length > 2) ? normalizeBotSource(trigger.intData[2], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); return ( @@ -40,11 +45,18 @@ export const WiredActionBotTalkToAvatarView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } - footer={ }> -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
+ footer={ +
+ +
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> +
+ }> + { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
}
{ LocalizeText('wiredfurni.params.message') } ('wired.action.bot.talk.to.avatar.max.length', 64) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> diff --git a/src/components/wired/views/actions/WiredActionBotTalkView.tsx b/src/components/wired/views/actions/WiredActionBotTalkView.tsx index 31b6a14..ab1ac60 100644 --- a/src/components/wired/views/actions/WiredActionBotTalkView.tsx +++ b/src/components/wired/views/actions/WiredActionBotTalkView.tsx @@ -4,36 +4,43 @@ import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; +import { BOT_SOURCES, WiredSourcesSelector } from '../WiredSourcesSelector'; + +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCES.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotTalkView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); const [ message, setMessage ] = useState(''); const [ talkMode, setTalkMode ] = useState(-1); + const [ botSource, setBotSource ] = useState(100); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); const save = () => { - setStringParam(botName + WIRED_STRING_DELIMETER + message); - setIntParams([ talkMode ]); + setStringParam(((botSource === 100) ? botName : '') + WIRED_STRING_DELIMETER + message); + setIntParams([ talkMode, botSource ]); }; useEffect(() => { const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + const nextBotName = (data.length > 0) ? data[0] : ''; - if(data.length > 0) setBotName(data[0]); + if(data.length > 0) setBotName(nextBotName); if(data.length > 1) setMessage(data[1].length > 0 ? data[1] : ''); setTalkMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + setBotSource((trigger.intData.length > 1) ? normalizeBotSource(trigger.intData[1], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); return ( - -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> }> + { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
}
{ LocalizeText('wiredfurni.params.message') } ('wired.action.bot.talk.max.length', 64) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> diff --git a/src/components/wired/views/actions/WiredActionBotTeleportView.tsx b/src/components/wired/views/actions/WiredActionBotTeleportView.tsx index 725f236..1422b36 100644 --- a/src/components/wired/views/actions/WiredActionBotTeleportView.tsx +++ b/src/components/wired/views/actions/WiredActionBotTeleportView.tsx @@ -4,11 +4,14 @@ import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { NitroInput } from '../../../../layout'; import { WiredActionBaseView } from './WiredActionBaseView'; -import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { BOT_SOURCES, WiredSourcesSelector } from '../WiredSourcesSelector'; + +const normalizeBotSource = (value: number, hasBotName = false) => (BOT_SOURCES.some(option => (option.value === value)) ? value : (hasBotName ? 100 : 0)); export const WiredActionBotTeleportView: FC<{}> = props => { const [ botName, setBotName ] = useState(''); + const [ botSource, setBotSource ] = useState(100); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); const [ furniSource, setFurniSource ] = useState(() => @@ -19,18 +22,21 @@ export const WiredActionBotTeleportView: FC<{}> = props => const save = () => { - setStringParam(botName); - setIntParams([ furniSource ]); + setStringParam((botSource === 100) ? botName : ''); + setIntParams([ furniSource, botSource ]); }; useEffect(() => { if(!trigger) return; - setBotName(trigger.stringData); + const nextBotName = trigger.stringData || ''; + setBotName(nextBotName); if(trigger.intData.length >= 1) setFurniSource(trigger.intData[0]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); + + setBotSource((trigger.intData.length >= 2) ? normalizeBotSource(trigger.intData[1], (nextBotName.length > 0)) : normalizeBotSource(-1, (nextBotName.length > 0))); }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); @@ -42,11 +48,18 @@ export const WiredActionBotTeleportView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } - footer={ }> -
- { LocalizeText('wiredfurni.params.bot.name') } - setBotName(event.target.value) } /> -
+ footer={ +
+ +
+ setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> +
+ }> + { (botSource === 100) && +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
} ); }; diff --git a/src/components/wired/views/actions/WiredActionControlClockView.tsx b/src/components/wired/views/actions/WiredActionControlClockView.tsx new file mode 100644 index 0000000..cba4691 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionControlClockView.tsx @@ -0,0 +1,82 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const COUNTER_INTERACTION_TYPES = [ 'game_upcounter' ]; + +const CONTROL_OPTIONS = [ + { value: 0, label: 'wiredfurni.params.clock_control.0' }, + { value: 1, label: 'wiredfurni.params.clock_control.1' }, + { value: 2, label: 'wiredfurni.params.clock_control.2' }, + { value: 3, label: 'wiredfurni.params.clock_control.3' }, + { value: 4, label: 'wiredfurni.params.clock_control.4' } +]; + +const normalizeControl = (value: number) => +{ + if(value < 0 || value > 4) return 0; + + return value; +}; + +export const WiredActionControlClockView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired(); + const [ control, setControl ] = useState(0); + const [ furniSource, setFurniSource ] = useState(() => + { + if(trigger?.intData?.length > 1) return trigger.intData[1]; + return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; + }); + + const save = () => + { + setIntParams([ + control, + furniSource + ]); + }; + + useEffect(() => + { + if(!trigger) return; + + setControl((trigger.intData.length > 0) ? normalizeControl(trigger.intData[0]) : 0); + setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0)); + }, [ trigger ]); + + useEffect(() => + { + setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES); + setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni'); + + return () => + { + setAllowedInteractionTypes(null); + setAllowedInteractionErrorKey(null); + }; + }, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]); + + return ( + }> +
+ { CONTROL_OPTIONS.map(option => + { + return ( +
+ setControl(option.value) } /> + { LocalizeText(option.label) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx b/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx index 1cfe779..be20004 100644 --- a/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx +++ b/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx @@ -7,24 +7,24 @@ import { WiredActionBaseView } from './WiredActionBaseView'; export const WiredActionGiveScoreToPredefinedTeamView: FC<{}> = props => { const [ points, setPoints ] = useState(1); - const [ time, setTime ] = useState(1); + const [ operation, setOperation ] = useState(0); const [ selectedTeam, setSelectedTeam ] = useState(1); const { trigger = null, setIntParams = null } = useWired(); - const save = () => setIntParams([ points, time, selectedTeam ]); + const save = () => setIntParams([ points, operation, selectedTeam ]); useEffect(() => { - if(trigger.intData.length >= 2) + if(trigger.intData.length >= 3) { setPoints(trigger.intData[0]); - setTime(trigger.intData[1]); + setOperation(trigger.intData[1]); setSelectedTeam(trigger.intData[2]); } else { setPoints(1); - setTime(1); + setOperation(0); setSelectedTeam(1); } }, [ trigger ]); @@ -40,12 +40,13 @@ export const WiredActionGiveScoreToPredefinedTeamView: FC<{}> = props => onChange={ event => setPoints(event) } />
- { LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) } - setTime(event) } /> + { LocalizeText('wiredfurni.params.choose_type') } + { [ 0, 1 ].map(value => ( + + )) }
{ LocalizeText('wiredfurni.params.team') } diff --git a/src/components/wired/views/actions/WiredActionGiveScoreView.tsx b/src/components/wired/views/actions/WiredActionGiveScoreView.tsx index 402b69d..0377a10 100644 --- a/src/components/wired/views/actions/WiredActionGiveScoreView.tsx +++ b/src/components/wired/views/actions/WiredActionGiveScoreView.tsx @@ -8,7 +8,7 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector'; export const WiredActionGiveScoreView: FC<{}> = props => { const [ points, setPoints ] = useState(1); - const [ time, setTime ] = useState(1); + const [ operation, setOperation ] = useState(0); const { trigger = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -16,19 +16,19 @@ export const WiredActionGiveScoreView: FC<{}> = props => return 0; }); - const save = () => setIntParams([ points, time, userSource ]); + const save = () => setIntParams([ points, operation, userSource ]); useEffect(() => { if(trigger.intData.length >= 2) { setPoints(trigger.intData[0]); - setTime(trigger.intData[1]); + setOperation(trigger.intData[1]); } else { setPoints(1); - setTime(1); + setOperation(0); } setUserSource((trigger.intData.length > 2) ? trigger.intData[2] : 0); @@ -49,12 +49,13 @@ export const WiredActionGiveScoreView: FC<{}> = props => onChange={ event => setPoints(event) } />
- { LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) } - setTime(event) } /> + { LocalizeText('wiredfurni.params.choose_type') } + { [ 0, 1 ].map(value => ( + + )) }
); diff --git a/src/components/wired/views/actions/WiredActionJoinTeamView.tsx b/src/components/wired/views/actions/WiredActionJoinTeamView.tsx index 3468f26..c0c9b74 100644 --- a/src/components/wired/views/actions/WiredActionJoinTeamView.tsx +++ b/src/components/wired/views/actions/WiredActionJoinTeamView.tsx @@ -7,20 +7,32 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector'; export const WiredActionJoinTeamView: FC<{}> = props => { - const [ selectedTeam, setSelectedTeam ] = useState(-1); + const [ selectedTeamType, setSelectedTeamType ] = useState(0); + const [ selectedTeam, setSelectedTeam ] = useState(1); const { trigger = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { + if(trigger?.intData?.length > 2) return trigger.intData[2]; if(trigger?.intData?.length > 1) return trigger.intData[1]; return 0; }); - const save = () => setIntParams([ selectedTeam, userSource ]); + const save = () => setIntParams([ selectedTeamType, selectedTeam, userSource ]); useEffect(() => { - setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 0); - setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + if(trigger.intData.length > 2) + { + setSelectedTeamType(trigger.intData[0]); + setSelectedTeam(trigger.intData[1]); + setUserSource(trigger.intData[2]); + } + else + { + setSelectedTeamType(0); + setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 1); + setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + } }, [ trigger ]); return ( @@ -29,6 +41,22 @@ export const WiredActionJoinTeamView: FC<{}> = props => requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } footer={ }> +
+ { LocalizeText('wiredfurni.params.choose_type') } + { [ + { value: 0, label: 'Wired' }, + { value: 1, label: 'Banzai' }, + { value: 2, label: 'Freeze' } + ].map(option => + { + return ( +
+ setSelectedTeamType(option.value) } /> + { option.label } +
+ ); + }) } +
{ LocalizeText('wiredfurni.params.team') } { [ 1, 2, 3, 4 ].map(team => diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx index 551da1a..baa0278 100644 --- a/src/components/wired/views/actions/WiredActionLayoutView.tsx +++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx @@ -1,13 +1,28 @@ import { WiredActionLayoutCode } from '../../../../api'; import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView'; +import { WiredActionAdjustClockView } from './WiredActionAdjustClockView'; import { WiredActionFreezeView } from './WiredActionFreezeView'; +import { WiredActionControlClockView } from './WiredActionControlClockView'; import { WiredActionFurniToFurniView } from './WiredActionFurniToFurniView'; +import { WiredActionSetAltitudeView } from './WiredActionSetAltitudeView'; import { WiredActionSendSignalView } from './WiredActionSendSignalView'; import { WiredActionFurniAreaView } from '../selectors/WiredActionFurniAreaView'; import { WiredSelectorFurniNeighborhoodView } from '../selectors/WiredSelectorFurniNeighborhoodView'; import { WiredSelectorFurniByTypeView } from '../selectors/WiredSelectorFurniByTypeView'; +import { WiredSelectorFurniAltitudeView } from '../selectors/WiredSelectorFurniAltitudeView'; +import { WiredSelectorFurniOnFurniView } from '../selectors/WiredSelectorFurniOnFurniView'; +import { WiredSelectorFurniPicksView } from '../selectors/WiredSelectorFurniPicksView'; +import { WiredSelectorFurniSignalView } from '../selectors/WiredSelectorFurniSignalView'; import { WiredSelectorUsersAreaView } from '../selectors/WiredSelectorUsersAreaView'; +import { WiredSelectorUsersByTypeView } from '../selectors/WiredSelectorUsersByTypeView'; +import { WiredSelectorUsersByActionView } from '../selectors/WiredSelectorUsersByActionView'; +import { WiredSelectorUsersByNameView } from '../selectors/WiredSelectorUsersByNameView'; +import { WiredSelectorUsersOnFurniView } from '../selectors/WiredSelectorUsersOnFurniView'; +import { WiredSelectorUsersGroupView } from '../selectors/WiredSelectorUsersGroupView'; +import { WiredSelectorUsersHandItemView } from '../selectors/WiredSelectorUsersHandItemView'; import { WiredSelectorUsersNeighborhoodView } from '../selectors/WiredSelectorUsersNeighborhoodView'; +import { WiredSelectorUsersSignalView } from '../selectors/WiredSelectorUsersSignalView'; +import { WiredSelectorUsersTeamView } from '../selectors/WiredSelectorUsersTeamView'; import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView'; import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView'; import { WiredActionBotMoveView } from './WiredActionBotMoveView'; @@ -25,6 +40,7 @@ import { WiredActionJoinTeamView } from './WiredActionJoinTeamView'; import { WiredActionKickFromRoomView } from './WiredActionKickFromRoomView'; import { WiredActionLeaveTeamView } from './WiredActionLeaveTeamView'; import { WiredActionMoveAndRotateFurniView } from './WiredActionMoveAndRotateFurniView'; +import { WiredActionMoveRotateUserView } from './WiredActionMoveRotateUserView'; import { WiredActionMoveFurniToView } from './WiredActionMoveFurniToView'; import { WiredActionMoveFurniView } from './WiredActionMoveFurniView'; import { WiredActionMuteUserView } from './WiredActionMuteUserView'; @@ -34,6 +50,8 @@ import { WiredActionSetFurniStateToView } from './WiredActionSetFurniStateToView import { WiredActionTeleportView } from './WiredActionTeleportView'; import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView'; import { WiredActionUnfreezeView } from './WiredActionUnfreezeView'; +import { WiredExtraFilterFurniView } from '../extras/WiredExtraFilterFurniView'; +import { WiredExtraFilterUserView } from '../extras/WiredExtraFilterUserView'; export const WiredActionLayoutView = (code: number) => { @@ -41,6 +59,8 @@ export const WiredActionLayoutView = (code: number) => { case WiredActionLayoutCode.BOT_CHANGE_FIGURE: return ; + case WiredActionLayoutCode.ADJUST_CLOCK: + return ; case WiredActionLayoutCode.BOT_FOLLOW_AVATAR: return ; case WiredActionLayoutCode.BOT_GIVE_HAND_ITEM: @@ -63,10 +83,14 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.FREEZE: return ; + case WiredActionLayoutCode.CONTROL_CLOCK: + return ; case WiredActionLayoutCode.FURNI_TO_USER: return ; case WiredActionLayoutCode.FURNI_TO_FURNI: return ; + case WiredActionLayoutCode.SET_ALTITUDE: + return ; case WiredActionLayoutCode.GIVE_REWARD: return ; case WiredActionLayoutCode.GIVE_SCORE: @@ -83,6 +107,8 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.MOVE_AND_ROTATE_FURNI: return ; + case WiredActionLayoutCode.MOVE_ROTATE_USER: + return ; case WiredActionLayoutCode.MOVE_FURNI_TO: return ; case WiredActionLayoutCode.MUTE_USER: @@ -107,10 +133,38 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.FURNI_BYTYPE_SELECTOR: return ; + case WiredActionLayoutCode.FURNI_ALTITUDE_SELECTOR: + return ; + case WiredActionLayoutCode.FURNI_ON_FURNI_SELECTOR: + return ; + case WiredActionLayoutCode.FURNI_PICKS_SELECTOR: + return ; + case WiredActionLayoutCode.FURNI_SIGNAL_SELECTOR: + return ; case WiredActionLayoutCode.USERS_AREA_SELECTOR: return ; case WiredActionLayoutCode.USERS_NEIGHBORHOOD_SELECTOR: return ; + case WiredActionLayoutCode.USERS_SIGNAL_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_BY_TYPE_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_BY_ACTION_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_BY_NAME_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_ON_FURNI_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_GROUP_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_HANDITEM_SELECTOR: + return ; + case WiredActionLayoutCode.USERS_TEAM_SELECTOR: + return ; + case WiredActionLayoutCode.FILTER_FURNI_EXTRA: + return ; + case WiredActionLayoutCode.FILTER_USER_EXTRA: + return ; case WiredActionLayoutCode.SEND_SIGNAL: return ; } diff --git a/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx b/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx index c7fff0d..b253590 100644 --- a/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx +++ b/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx @@ -2,34 +2,17 @@ import { FC, useEffect, useState } from 'react'; import { LocalizeText, WiredFurniType } from '../../../../api'; import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; +import { WiredDirectionIcon, WIRED_DIRECTION_GRID } from '../WiredDirectionIcon'; import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -const directionOptions: { value: number, icon: string }[] = [ - { - value: 0, - icon: 'ne' - }, - { - value: 2, - icon: 'se' - }, - { - value: 4, - icon: 'sw' - }, - { - value: 6, - icon: 'nw' - } -]; - const rotationOptions: number[] = [ 0, 1, 2, 3, 4, 5, 6 ]; export const WiredActionMoveAndRotateFurniView: FC<{}> = props => { const [ movement, setMovement ] = useState(-1); const [ rotation, setRotation ] = useState(-1); + const [ blockOnUserCollision, setBlockOnUserCollision ] = useState(false); const { trigger = null, setIntParams = null } = useWired(); const [ furniSource, setFurniSource ] = useState(() => { @@ -37,7 +20,7 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props => return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; }); - const save = () => setIntParams([ movement, rotation, furniSource ]); + const save = () => setIntParams([ movement, rotation, furniSource, blockOnUserCollision ? 1 : 0 ]); useEffect(() => { @@ -54,6 +37,8 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props => if(trigger.intData.length > 2) setFurniSource(trigger.intData[2]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); + + setBlockOnUserCollision((trigger.intData?.length ?? 0) > 3 ? trigger.intData[3] === 1 : false); }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); @@ -68,18 +53,23 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props => footer={ }>
{ LocalizeText('wiredfurni.params.startdir') } -
- { directionOptions.map(option => +
+ { WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) => { + if(direction === null) + { + return
; + } + return ( -
- setMovement(option.value) } /> - - - -
+ ); - }) } + })) }
@@ -94,6 +84,13 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props => ); }) }
+
+ { LocalizeText('wiredfurni.params.user_collide') } + +
); }; diff --git a/src/components/wired/views/actions/WiredActionMoveFurniView.tsx b/src/components/wired/views/actions/WiredActionMoveFurniView.tsx index e7285a3..391e726 100644 --- a/src/components/wired/views/actions/WiredActionMoveFurniView.tsx +++ b/src/components/wired/views/actions/WiredActionMoveFurniView.tsx @@ -2,37 +2,44 @@ import { FC, useEffect, useState } from 'react'; import { LocalizeText, WiredFurniType } from '../../../../api'; import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; +import iconWiredDirE from '../../../../assets/images/wired/icon_wired_dir_e.png'; +import iconWiredDirHorizontalRandom from '../../../../assets/images/wired/icon_wired_dir_horizontal_random.png'; +import iconWiredDirN from '../../../../assets/images/wired/icon_wired_dir_n.png'; +import iconWiredDirNe from '../../../../assets/images/wired/icon_wired_dir_ne.png'; +import iconWiredDirNw from '../../../../assets/images/wired/icon_wired_dir_nw.png'; +import iconWiredDirRandom from '../../../../assets/images/wired/icon_wired_dir_random.png'; +import iconWiredDirS from '../../../../assets/images/wired/icon_wired_dir_s.png'; +import iconWiredDirSe from '../../../../assets/images/wired/icon_wired_dir_se.png'; +import iconWiredDirSw from '../../../../assets/images/wired/icon_wired_dir_sw.png'; +import iconWiredDirVerticalRandom from '../../../../assets/images/wired/icon_wired_dir_vertical_random.png'; +import iconWiredDirW from '../../../../assets/images/wired/icon_wired_dir_w.png'; +import { WiredDirectionIcon, WIRED_DIRECTION_GRID } from '../WiredDirectionIcon'; import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -const directionOptions: { value: number, icon: string }[] = [ +const NORMAL_DIRECTION_VALUE_MAP: Record = { + 0: { value: 6, icon: iconWiredDirN }, + 1: { value: 8, icon: iconWiredDirNe }, + 2: { value: 5, icon: iconWiredDirE }, + 3: { value: 9, icon: iconWiredDirSe }, + 4: { value: 4, icon: iconWiredDirS }, + 5: { value: 10, icon: iconWiredDirSw }, + 6: { value: 7, icon: iconWiredDirW }, + 7: { value: 11, icon: iconWiredDirNw } +}; + +const extraDirectionOptions: { value: number, icon: string }[] = [ { - value: 4, - icon: 'ne' - }, - { - value: 5, - icon: 'se' - }, - { - value: 6, - icon: 'sw' - }, - { - value: 7, - icon: 'nw' + value: 1, + icon: iconWiredDirRandom }, { value: 2, - icon: 'mv-2' + icon: iconWiredDirHorizontalRandom }, { value: 3, - icon: 'mv-3' - }, - { - value: 1, - icon: 'mv-1' + icon: iconWiredDirVerticalRandom } ]; @@ -84,17 +91,38 @@ export const WiredActionMoveFurniView: FC<{}> = props => setMovement(0) } /> { LocalizeText('wiredfurni.params.movefurni.0') }
-
- { directionOptions.map(option => +
+ { WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) => + { + if(direction === null) + { + return
; + } + + const option = NORMAL_DIRECTION_VALUE_MAP[direction]; + + return ( + + ); + })) } +
+
+ { extraDirectionOptions.map(option => { return ( -
+
+ + + + ); }) } -
diff --git a/src/components/wired/views/actions/WiredActionMoveRotateUserView.tsx b/src/components/wired/views/actions/WiredActionMoveRotateUserView.tsx new file mode 100644 index 0000000..c39d5f6 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveRotateUserView.tsx @@ -0,0 +1,121 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import iconRotateClockwise from '../../../../assets/images/wired/icon_wired_rotate_clockwise.png'; +import iconRotateCounterClockwise from '../../../../assets/images/wired/icon_wired_rotate_counter_clockwise.png'; +import { WiredDirectionIcon, WIRED_DIRECTION_GRID } from '../WiredDirectionIcon'; +import { WiredActionBaseView } from './WiredActionBaseView'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; + +const ROTATION_CLOCKWISE = 8; +const ROTATION_COUNTER_CLOCKWISE = 9; + +interface DirectionExtraOption +{ + value: number; + icon: string; + label: string; +} + +interface DirectionPickerProps +{ + name: string; + title: string; + noneLabel: string; + value: number; + onChange: (value: number) => void; + extraOptions?: DirectionExtraOption[]; +} + +const DirectionPicker: FC = props => +{ + const { name = '', title = '', noneLabel = '', value = -1, onChange = null, extraOptions = [] } = props; + + return ( +
+ { title } + +
+ { WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) => + { + if(direction === null) + { + return
; + } + + const selected = (value === direction); + + return ( + + ); + })) } +
+ { extraOptions.length > 0 && +
+ { extraOptions.map(option => ( + + )) } +
} +
+ ); +}; + +export const WiredActionMoveRotateUserView: FC<{}> = props => +{ + const [ movementDirection, setMovementDirection ] = useState(-1); + const [ rotationDirection, setRotationDirection ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + const [ userSource, setUserSource ] = useState(() => + { + if(trigger?.intData?.length > 2) return trigger.intData[2]; + return 0; + }); + + const save = () => setIntParams([ movementDirection, rotationDirection, userSource ]); + + const rotationExtraOptions: DirectionExtraOption[] = [ + { value: ROTATION_CLOCKWISE, icon: iconRotateClockwise, label: LocalizeText('wiredfurni.params.rotatefurni.1') }, + { value: ROTATION_COUNTER_CLOCKWISE, icon: iconRotateCounterClockwise, label: LocalizeText('wiredfurni.params.rotatefurni.2') } + ]; + + useEffect(() => + { + setMovementDirection((trigger.intData.length > 0) ? trigger.intData[0] : -1); + setRotationDirection((trigger.intData.length > 1) ? trigger.intData[1] : -1); + setUserSource((trigger.intData.length > 2) ? trigger.intData[2] : 0); + }, [ trigger ]); + + return ( + }> + + + + ); +}; diff --git a/src/components/wired/views/actions/WiredActionSendSignalView.tsx b/src/components/wired/views/actions/WiredActionSendSignalView.tsx index 6ed3df5..af6a253 100644 --- a/src/components/wired/views/actions/WiredActionSendSignalView.tsx +++ b/src/components/wired/views/actions/WiredActionSendSignalView.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; import { Button, Text } from '../../../../common'; import { useWired } from '../../../../hooks'; @@ -48,6 +48,7 @@ export const WiredActionSendSignalView: FC<{}> = () => const [ antennaIds, setAntennaIds ] = useState([]); const [ forwardFurniIds, setForwardFurniIds ] = useState([]); const [ selectionMode, setSelectionMode ] = useState('antenna'); + const highlightedIds = useRef([]); const { trigger = null, furniIds = [], setFurniIds = null, setIntParams = null, setStringParam = null, setAllowedInteractionTypes = null } = useWired(); @@ -84,28 +85,39 @@ export const WiredActionSendSignalView: FC<{}> = () => else setForwardFurniIds(furniIds); }, [ furniIds, selectionMode ]); - const applySelection = useCallback((nextIds: number[]) => + const syncHighlights = useCallback((nextAntennaIds: number[], nextForwardFurniIds: number[], nextSelectionMode: SelectionMode, nextFurniSource: number) => { - if(!setFurniIds) return; - - setFurniIds(prev => + if(highlightedIds.current.length) { - if(prev && prev.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prev); - if(nextIds && nextIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(nextIds); + WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current); + WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current); + } - return [ ...nextIds ]; - }); - }, [ setFurniIds ]); + const visibleForwardIds = (nextFurniSource === SOURCE_SELECTED) ? nextForwardFurniIds : []; + const activeIds = (nextSelectionMode === 'antenna') ? nextAntennaIds : visibleForwardIds; + const passiveIds = (nextSelectionMode === 'antenna') ? visibleForwardIds : nextAntennaIds; + const activeSet = new Set(activeIds); + const passiveOnlyIds = passiveIds.filter(id => !activeSet.has(id)); + + if(activeIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(activeIds); + if(passiveOnlyIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(passiveOnlyIds); + + highlightedIds.current = Array.from(new Set([ ...activeIds, ...passiveOnlyIds ])); + }, []); + + useEffect(() => + { + syncHighlights(antennaIds, forwardFurniIds, selectionMode, furniSource); + }, [ antennaIds, forwardFurniIds, selectionMode, furniSource, syncHighlights ]); 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 ]); + if(setFurniIds) setFurniIds([ ...((mode === 'antenna') ? antennaIds : forwardFurniIds) ]); + }, [ selectionMode, furniSource, antennaIds, forwardFurniIds, setFurniIds ]); const onChangeFurniSource = (next: number) => { @@ -113,7 +125,7 @@ export const WiredActionSendSignalView: FC<{}> = () => if(selectionMode === 'furni') { - applySelection(antennaIds); + if(setFurniIds) setFurniIds([ ...antennaIds ]); setSelectionMode('antenna'); } @@ -122,12 +134,6 @@ export const WiredActionSendSignalView: FC<{}> = () => const save = useCallback(() => { - if(selectionMode === 'furni') - { - setSelectionMode('antenna'); - applySelection(antennaIds); - } - const antennaSource = (antennaIds && antennaIds.length) ? antennaIds[0] : 0; setIntParams([ @@ -140,7 +146,19 @@ export const WiredActionSendSignalView: FC<{}> = () => ]); setStringParam(serializeForwardIds(forwardFurniIds)); - }, [ selectionMode, antennaIds, furniSource, userSource, signalPerFurni, signalPerUser, forwardFurniIds, setIntParams, setStringParam, applySelection, setSelectionMode ]); + }, [ antennaIds, furniSource, userSource, signalPerFurni, signalPerUser, forwardFurniIds, setIntParams, setStringParam ]); + + useEffect(() => + { + return () => + { + if(!highlightedIds.current.length) return; + + WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current); + WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current); + highlightedIds.current = []; + }; + }, []); const selectionLimit = trigger?.maximumItemSelectionCount ?? 0; const forwardSelectionEnabled = (furniSource === SOURCE_SELECTED); diff --git a/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx b/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx index 8f31754..3f2c8cb 100644 --- a/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx +++ b/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx @@ -10,22 +10,26 @@ export const WiredActionSetFurniStateToView: FC<{}> = props => const [ stateFlag, setStateFlag ] = useState(0); const [ directionFlag, setDirectionFlag ] = useState(0); const [ positionFlag, setPositionFlag ] = useState(0); + const [ altitudeFlag, setAltitudeFlag ] = useState(0); const { trigger = null, setIntParams = null } = useWired(); const [ furniSource, setFurniSource ] = useState(() => { + if(trigger?.intData?.length > 4) return trigger.intData[4]; if(trigger?.intData?.length > 3) return trigger.intData[3]; return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; }); - const save = () => setIntParams([ stateFlag, directionFlag, positionFlag, furniSource ]); + const save = () => setIntParams([ stateFlag, directionFlag, positionFlag, altitudeFlag, furniSource ]); useEffect(() => { setStateFlag(trigger.getBoolean(0) ? 1 : 0); setDirectionFlag(trigger.getBoolean(1) ? 1 : 0); setPositionFlag(trigger.getBoolean(2) ? 1 : 0); + setAltitudeFlag((trigger.intData.length > 4 && trigger.getBoolean(3)) ? 1 : 0); - if(trigger.intData.length > 3) setFurniSource(trigger.intData[3]); + if(trigger.intData.length > 4) setFurniSource(trigger.intData[4]); + else if(trigger.intData.length > 3) setFurniSource(trigger.intData[3]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); }, [ trigger ]); @@ -53,6 +57,10 @@ export const WiredActionSetFurniStateToView: FC<{}> = props => setPositionFlag(event.target.checked ? 1 : 0) } /> { LocalizeText('wiredfurni.params.condition.position') }
+
+ setAltitudeFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.altitude') } +
); diff --git a/src/components/wired/views/actions/WiredActionTeleportView.tsx b/src/components/wired/views/actions/WiredActionTeleportView.tsx index b526092..c26c7bb 100644 --- a/src/components/wired/views/actions/WiredActionTeleportView.tsx +++ b/src/components/wired/views/actions/WiredActionTeleportView.tsx @@ -1,21 +1,39 @@ import { FC, useEffect, useState } from 'react'; -import { WiredFurniType } from '../../../../api'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { WiredActionBaseView } from './WiredActionBaseView'; +import { WiredActionLayoutCode } from '../../../../api/wired/WiredActionLayoutCode'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; export const WiredActionTeleportView: FC<{}> = props => { const { trigger = null, setIntParams = null } = useWired(); + const isTeleportEffect = (trigger?.code === WiredActionLayoutCode.TELEPORT); + const isUserToFurniEffect = (trigger?.code === WiredActionLayoutCode.USER_TO_FURNI); + const [ fastTeleport, setFastTeleport ] = useState(() => + { + if(isTeleportEffect && trigger?.intData?.length >= 3) return (trigger.intData[0] === 1); + return false; + }); + const [ walkMode, setWalkMode ] = useState(() => + { + if(isUserToFurniEffect && trigger?.intData?.length >= 3) return trigger.intData[2]; + return 1; + }); const [ furniSource, setFurniSource ] = useState(() => { + if(isTeleportEffect && trigger?.intData?.length >= 3) return trigger.intData[1]; + if(isUserToFurniEffect && trigger?.intData?.length >= 3) return trigger.intData[0]; if(trigger?.intData?.length >= 1) return trigger.intData[0]; return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; }); const [ userSource, setUserSource ] = useState(() => { + if(isTeleportEffect && trigger?.intData?.length >= 3) return trigger.intData[2]; + if(isUserToFurniEffect && trigger?.intData?.length >= 3) return trigger.intData[1]; if(trigger?.intData?.length >= 2) return trigger.intData[1]; return 0; }); @@ -24,16 +42,37 @@ export const WiredActionTeleportView: FC<{}> = props => { if(!trigger) return; + if(isTeleportEffect && trigger.intData.length >= 3) + { + setFastTeleport(trigger.intData[0] === 1); + setFurniSource(trigger.intData[1]); + setUserSource(trigger.intData[2]); + setWalkMode(1); + return; + } + + if(isUserToFurniEffect && trigger.intData.length >= 3) + { + setFastTeleport(false); + setFurniSource(trigger.intData[0]); + setUserSource(trigger.intData[1]); + setWalkMode(trigger.intData[2]); + return; + } + + setFastTeleport(false); + setWalkMode(1); + if(trigger.intData.length >= 1) setFurniSource(trigger.intData[0]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); if(trigger.intData.length >= 2) setUserSource(trigger.intData[1]); else setUserSource(0); - }, [ trigger ]); + }, [ isTeleportEffect, isUserToFurniEffect, trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); - const save = () => setIntParams([ furniSource, userSource ]); + const save = () => setIntParams(isTeleportEffect ? [ fastTeleport ? 1 : 0, furniSource, userSource ] : (isUserToFurniEffect ? [ furniSource, userSource, walkMode ] : [ furniSource, userSource ])); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT; @@ -50,6 +89,21 @@ export const WiredActionTeleportView: FC<{}> = props => userSource={ userSource } onChangeFurni={ onChangeFurniSource } onChangeUsers={ setUserSource } /> - } /> + }> + { isTeleportEffect && +
+ setFastTeleport(event.target.checked) } /> + { LocalizeText('wiredfurni.params.teleport.options.0') } +
} + { isUserToFurniEffect && +
+ { [ 0, 1, 2 ].map(option => ( + + )) } +
} + ); }; diff --git a/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx b/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx index 2055acf..4450906 100644 --- a/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx +++ b/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx @@ -1,5 +1,6 @@ import { FC, useEffect, useState } from 'react'; -import { WiredFurniType } from '../../../../api'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; import { useWired } from '../../../../hooks'; @@ -7,8 +8,14 @@ import { useWired } from '../../../../hooks'; export const WiredActionToggleFurniStateView: FC<{}> = props => { const { trigger = null, setIntParams = null } = useWired(); + const [ toggleType, setToggleType ] = useState(() => + { + if(trigger?.intData?.length > 1) return trigger.intData[0]; + return 0; + }); const [ furniSource, setFurniSource ] = useState(() => { + if(trigger?.intData?.length > 1) return trigger.intData[1]; if(trigger?.intData?.length >= 1) return trigger.intData[0]; return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; }); @@ -17,13 +24,26 @@ export const WiredActionToggleFurniStateView: FC<{}> = props => { if(!trigger) return; - if(trigger.intData.length >= 1) setFurniSource(trigger.intData[0]); - else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); + if(trigger.intData.length > 1) + { + setToggleType(trigger.intData[0]); + setFurniSource(trigger.intData[1]); + } + else if(trigger.intData.length >= 1) + { + setToggleType(0); + setFurniSource(trigger.intData[0]); + } + else + { + setToggleType(0); + setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); + } }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); - const save = () => setIntParams([ furniSource ]); + const save = () => setIntParams([ toggleType, furniSource ]); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT; @@ -32,6 +52,16 @@ export const WiredActionToggleFurniStateView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } - footer={ } /> + footer={ }> +
+ { LocalizeText('wiredfurni.params.operator.2') } + { [ 0, 1 ].map(option => ( + + )) } +
+ ); }; diff --git a/src/components/wired/views/conditions/WiredConditionActorDirView.tsx b/src/components/wired/views/conditions/WiredConditionActorDirView.tsx new file mode 100644 index 0000000..127e754 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorDirView.tsx @@ -0,0 +1,100 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredDirectionIcon, WIRED_DIRECTION_GRID } from '../WiredDirectionIcon'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const toggleDirection = (mask: number, direction: number, checked: boolean) => +{ + if(checked) return (mask | (1 << direction)); + + return (mask & ~(1 << direction)); +}; + +export const WiredConditionActorDirView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null } = useWired(); + const [ directionMask, setDirectionMask ] = useState(0); + const [ userSource, setUserSource ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); + const [ showAdvanced, setShowAdvanced ] = useState(false); + + useEffect(() => + { + if(!trigger) return; + + const nextDirectionMask = trigger.intData.length > 0 ? trigger.intData[0] : 0; + const nextUserSource = trigger.intData.length > 1 ? trigger.intData[1] : 0; + const nextQuantifier = trigger.intData.length > 2 ? trigger.intData[2] : 0; + + setDirectionMask(nextDirectionMask); + setUserSource(nextUserSource); + setQuantifier((nextQuantifier === 1) ? 1 : 0); + setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0); + }, [ trigger ]); + + const save = () => + { + setIntParams([ + directionMask, + userSource, + quantifier + ]); + }; + + return ( + + + { showAdvanced && + <> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( + + ); + }) } +
+ + } +
+ }> +
+ { LocalizeText('wiredfurni.params.direction_selection') } +
+ { WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) => + { + if(direction === null) + { + return
; + } + + const checked = ((directionMask & (1 << direction)) !== 0); + + return ( + + ); + })) } +
+
+ + ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx b/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx index 41df779..f190085 100644 --- a/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx +++ b/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx @@ -2,14 +2,19 @@ import { FC, useEffect, useState } from 'react'; import { LocalizeText, WiredFurniType } from '../../../../api'; import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; +import { WiredHandItemField } from '../WiredHandItemField'; import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -const ALLOWED_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; +interface WiredConditionActorHasHandItemViewProps +{ + negative?: boolean; +} -export const WiredConditionActorHasHandItemView: FC<{}> = props => +export const WiredConditionActorHasHandItemView: FC = ({ negative = false }) => { const [ handItemId, setHandItemId ] = useState(-1); + const [ quantifier, setQuantifier ] = useState(0); const { trigger = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -17,12 +22,13 @@ export const WiredConditionActorHasHandItemView: FC<{}> = props => return 0; }); - const save = () => setIntParams([ handItemId, userSource ]); + const save = () => setIntParams([ handItemId, userSource, quantifier ]); useEffect(() => { setHandItemId((trigger.intData.length > 0) ? trigger.intData[0] : 0); setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + setQuantifier((trigger.intData.length > 2 && trigger.intData[2] === 1) ? 1 : 0); }, [ trigger ]); return ( @@ -32,14 +38,15 @@ export const WiredConditionActorHasHandItemView: FC<{}> = props => save={ save } footer={ }>
- { LocalizeText('wiredfurni.params.handitem') } - + { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) }
+ ); }; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx index b75608d..c6f33af 100644 --- a/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx +++ b/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx @@ -1,32 +1,96 @@ -import { FC, useEffect, useState } from 'react'; -import { WiredFurniType } from '../../../../api'; -import { useWired } from '../../../../hooks'; -import { WiredConditionBaseView } from './WiredConditionBaseView'; +import { CatalogGroupsComposer, GuildMembershipsMessageEvent, HabboGroupEntryData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { LocalizeText, SendMessageComposer, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useMessageEvent, useWired } from '../../../../hooks'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; -export const WiredConditionActorIsGroupMemberView: FC<{}> = props => +const GROUP_CURRENT_ROOM = 0; +const GROUP_SELECTED = 1; + +interface WiredConditionActorIsGroupMemberViewProps { + negative?: boolean; +} + +export const WiredConditionActorIsGroupMemberView: FC = ({ negative = false }) => +{ + const [ groups, setGroups ] = useState([]); + const [ userSource, setUserSource ] = useState(0); + const [ groupType, setGroupType ] = useState(GROUP_CURRENT_ROOM); + const [ selectedGroupId, setSelectedGroupId ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); const { trigger = null, setIntParams = null } = useWired(); - const [ userSource, setUserSource ] = useState(() => + + useMessageEvent(GuildMembershipsMessageEvent, event => { - if(trigger?.intData?.length >= 1) return trigger.intData[0]; - return 0; + const parser = event.getParser(); + + setGroups(parser.groups || []); }); + useEffect(() => + { + SendMessageComposer(new CatalogGroupsComposer()); + }, []); + useEffect(() => { if(!trigger) return; - if(trigger.intData.length >= 1) setUserSource(trigger.intData[0]); - else setUserSource(0); + + const params = trigger.intData; + + setUserSource(params.length > 0 ? params[0] : 0); + setGroupType(params.length > 1 ? params[1] : GROUP_CURRENT_ROOM); + setSelectedGroupId(params.length > 2 ? params[2] : 0); + setQuantifier((params.length > 3 && params[3] === 1) ? 1 : 0); }, [ trigger ]); - const save = () => setIntParams([ userSource ]); + useEffect(() => + { + if((groupType !== GROUP_SELECTED) || selectedGroupId || !groups.length) return; + + setSelectedGroupId(groups[0].groupId); + }, [ groupType, selectedGroupId, groups ]); + + const selectedGroupOptions = useMemo(() => groups.map(group => ({ value: group.groupId, label: group.groupName })), [ groups ]); + + const save = () => setIntParams([ userSource, groupType, selectedGroupId, quantifier ]); return ( } /> + footer={ }> +
+
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
+ +
+ { LocalizeText('wiredfurni.params.groupselection') } + { [ GROUP_CURRENT_ROOM, GROUP_SELECTED ].map(value => ( + + )) } +
+ + { (groupType === GROUP_SELECTED) && + } +
+
); }; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx index 7796d23..90c245a 100644 --- a/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx +++ b/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx @@ -1,10 +1,16 @@ import { FC, useEffect, useState } from 'react'; -import { WiredFurniType } from '../../../../api'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -export const WiredConditionActorIsOnFurniView: FC<{}> = props => +interface WiredConditionActorIsOnFurniViewProps +{ + negative?: boolean; +} + +export const WiredConditionActorIsOnFurniView: FC = ({ negative = false }) => { const { trigger = null, setIntParams = null } = useWired(); const [ furniSource, setFurniSource ] = useState(() => @@ -17,6 +23,11 @@ export const WiredConditionActorIsOnFurniView: FC<{}> = props => if(trigger?.intData?.length > 1) return trigger.intData[1]; return 0; }); + const [ quantifier, setQuantifier ] = useState(() => + { + if(trigger?.intData?.length > 2) return trigger.intData[2]; + return 0; + }); useEffect(() => { @@ -27,11 +38,14 @@ export const WiredConditionActorIsOnFurniView: FC<{}> = props => if(trigger.intData.length > 1) setUserSource(trigger.intData[1]); else setUserSource(0); + + if(trigger.intData.length > 2) setQuantifier(trigger.intData[2] === 1 ? 1 : 0); + else setQuantifier(0); }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); - const save = () => setIntParams([ furniSource, userSource ]); + const save = () => setIntParams([ furniSource, userSource, quantifier ]); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID; @@ -41,13 +55,24 @@ export const WiredConditionActorIsOnFurniView: FC<{}> = props => requiresFurni={ requiresFurni } save={ save } footer={ ( - +
+
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
+ +
) } /> ); }; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx index 43ec3c9..dfac1da 100644 --- a/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx +++ b/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx @@ -7,9 +7,15 @@ import { WiredSourcesSelector } from '../WiredSourcesSelector'; const teamIds: number[] = [ 1, 2, 3, 4 ]; -export const WiredConditionActorIsTeamMemberView: FC<{}> = props => +interface WiredConditionActorIsTeamMemberViewProps +{ + negative?: boolean; +} + +export const WiredConditionActorIsTeamMemberView: FC = ({ negative = false }) => { const [ selectedTeam, setSelectedTeam ] = useState(-1); + const [ quantifier, setQuantifier ] = useState(1); const { trigger = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -17,12 +23,13 @@ export const WiredConditionActorIsTeamMemberView: FC<{}> = props => return 0; }); - const save = () => setIntParams([ selectedTeam, userSource ]); + const save = () => setIntParams([ selectedTeam, userSource, quantifier ]); useEffect(() => { setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 0); setUserSource((trigger.intData.length > 1) ? trigger.intData[1] : 0); + setQuantifier((trigger.intData.length > 2) ? (trigger.intData[2] === 1 ? 1 : 0) : 1); }, [ trigger ]); return ( @@ -31,6 +38,15 @@ export const WiredConditionActorIsTeamMemberView: FC<{}> = props => requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } footer={ }> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
{ LocalizeText('wiredfurni.params.team') } { teamIds.map(value => diff --git a/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx index d4f370e..34df63f 100644 --- a/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx +++ b/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx @@ -6,9 +6,15 @@ import { NitroInput } from '../../../../layout'; import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -export const WiredConditionActorIsWearingBadgeView: FC<{}> = props => +interface WiredConditionActorIsWearingBadgeViewProps +{ + negative?: boolean; +} + +export const WiredConditionActorIsWearingBadgeView: FC = ({ negative = false }) => { const [ badge, setBadge ] = useState(''); + const [ quantifier, setQuantifier ] = useState(1); const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -19,7 +25,7 @@ export const WiredConditionActorIsWearingBadgeView: FC<{}> = props => const save = () => { setStringParam(badge); - setIntParams([ userSource ]); + setIntParams([ userSource, quantifier ]); }; useEffect(() => @@ -27,6 +33,7 @@ export const WiredConditionActorIsWearingBadgeView: FC<{}> = props => setBadge(trigger.stringData); if(trigger.intData.length >= 1) setUserSource(trigger.intData[0]); else setUserSource(0); + setQuantifier((trigger.intData.length >= 2) ? (trigger.intData[1] === 1 ? 1 : 0) : 1); }, [ trigger ]); return ( @@ -35,6 +42,15 @@ export const WiredConditionActorIsWearingBadgeView: FC<{}> = props => requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } footer={ }> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
{ LocalizeText('wiredfurni.params.badgecode') } setBadge(event.target.value) } /> diff --git a/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx index d0e19e1..e18e82e 100644 --- a/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx +++ b/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx @@ -6,9 +6,15 @@ import { NitroInput } from '../../../../layout'; import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -export const WiredConditionActorIsWearingEffectView: FC<{}> = props => +interface WiredConditionActorIsWearingEffectViewProps +{ + negative?: boolean; +} + +export const WiredConditionActorIsWearingEffectView: FC = ({ negative = false }) => { const [ effect, setEffect ] = useState(-1); + const [ quantifier, setQuantifier ] = useState(1); const { trigger = null, setIntParams = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -16,13 +22,14 @@ export const WiredConditionActorIsWearingEffectView: FC<{}> = props => return 0; }); - const save = () => setIntParams([ effect, userSource ]); + const save = () => setIntParams([ effect, userSource, quantifier ]); useEffect(() => { setEffect(trigger?.intData[0] ?? 0); if(trigger?.intData?.length > 1) setUserSource(trigger.intData[1]); else setUserSource(0); + setQuantifier((trigger?.intData?.length > 2) ? (trigger.intData[2] === 1 ? 1 : 0) : 1); }, [ trigger ]); return ( @@ -31,6 +38,15 @@ export const WiredConditionActorIsWearingEffectView: FC<{}> = props => requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } footer={ }> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
{ LocalizeText('wiredfurni.tooltip.effectid') } setEffect(parseInt(event.target.value)) } /> diff --git a/src/components/wired/views/conditions/WiredConditionCounterTimeMatchesView.tsx b/src/components/wired/views/conditions/WiredConditionCounterTimeMatchesView.tsx new file mode 100644 index 0000000..6631f28 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionCounterTimeMatchesView.tsx @@ -0,0 +1,139 @@ +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 { WiredConditionBaseView } from './WiredConditionBaseView'; + +const COUNTER_INTERACTION_TYPES = [ 'game_upcounter' ]; +const MINUTES_MIN = 0; +const MINUTES_MAX = 99; +const HALF_SECONDS_MIN = 0; +const HALF_SECONDS_MAX = 119; + +const COMPARISON_OPTIONS = [ + { value: 0, label: 'wiredfurni.params.comparison.0' }, + { value: 1, label: 'wiredfurni.params.comparison.1' }, + { value: 2, label: 'wiredfurni.params.comparison.2' } +]; + +const normalizeComparison = (value: number) => +{ + if(value < 0 || value > 2) return 1; + + return value; +}; + +const normalizeMinutes = (value: number) => Math.max(MINUTES_MIN, Math.min(MINUTES_MAX, value)); +const normalizeHalfSeconds = (value: number) => Math.max(HALF_SECONDS_MIN, Math.min(HALF_SECONDS_MAX, value)); + +const formatSeconds = (halfSeconds: number) => +{ + const value = normalizeHalfSeconds(halfSeconds) / 2; + const text = value.toFixed(1); + + return text.endsWith('.0') ? text.slice(0, -2) : text; +}; + +export const WiredConditionCounterTimeMatchesView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired(); + const [ comparison, setComparison ] = useState(1); + const [ furniSource, setFurniSource ] = useState(() => + { + if(trigger?.intData?.length > 3) return trigger.intData[3]; + return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; + }); + const [ minutes, setMinutes ] = useState(0); + const [ halfSeconds, setHalfSeconds ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); + + const secondsLabel = useMemo(() => formatSeconds(halfSeconds), [ halfSeconds ]); + + const save = () => + { + setIntParams([ + normalizeComparison(comparison), + normalizeMinutes(minutes), + normalizeHalfSeconds(halfSeconds), + furniSource, + quantifier + ]); + }; + + useEffect(() => + { + if(!trigger) return; + + setComparison((trigger.intData.length > 0) ? normalizeComparison(trigger.intData[0]) : 1); + setMinutes((trigger.intData.length > 1) ? normalizeMinutes(trigger.intData[1]) : 0); + setHalfSeconds((trigger.intData.length > 2) ? normalizeHalfSeconds(trigger.intData[2]) : 0); + setFurniSource((trigger.intData.length > 3) ? trigger.intData[3] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0)); + setQuantifier((trigger.intData.length > 4 && trigger.intData[4] === 1) ? 1 : 0); + }, [ trigger ]); + + useEffect(() => + { + setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES); + setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni'); + + return () => + { + setAllowedInteractionTypes(null); + setAllowedInteractionErrorKey(null); + }; + }, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
+ +
+ }> +
+ { COMPARISON_OPTIONS.map(option => + { + return ( +
+ setComparison(option.value) } /> + { LocalizeText(option.label) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.clock_minutes_elapsed', [ 'minutes' ], [ minutes.toString() ]) } + setMinutes(normalizeMinutes(event as number)) } /> + { minutes } +
+
+ { LocalizeText('wiredfurni.params.clock_seconds_elapsed', [ 'seconds' ], [ secondsLabel ]) } + setHalfSeconds(normalizeHalfSeconds(event as number)) } /> + { secondsLabel } +
+ + ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx index aa4505f..76db5a9 100644 --- a/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx +++ b/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx @@ -1,28 +1,41 @@ import { FC, useEffect, useState } from 'react'; -import { WiredFurniType } from '../../../../api'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -export const WiredConditionFurniHasAvatarOnView: FC<{}> = props => +interface WiredConditionFurniHasAvatarOnViewProps +{ + negative?: boolean; +} + +export const WiredConditionFurniHasAvatarOnView: FC = ({ negative = false }) => { const { trigger = null, setIntParams = null } = useWired(); + const [ requireAll, setRequireAll ] = useState(0); const [ furniSource, setFurniSource ] = useState(() => { - if(trigger?.intData?.length >= 1) return trigger.intData[0]; + if(trigger?.intData?.length > 1) return trigger.intData[1]; + if(trigger?.intData?.length >= 1 && trigger.intData[0] > 1) return trigger.intData[0]; return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; }); useEffect(() => { if(!trigger) return; - if(trigger.intData.length >= 1) setFurniSource(trigger.intData[0]); + + if(trigger.intData.length >= 1) setRequireAll(trigger.intData[0] === 1 ? 1 : 0); + else setRequireAll(0); + + if(trigger.intData.length > 1) setFurniSource(trigger.intData[1]); + else if(trigger.intData.length >= 1 && trigger.intData[0] > 1) setFurniSource(trigger.intData[0]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); - const save = () => setIntParams([ furniSource ]); + const save = () => setIntParams([ requireAll, furniSource ]); const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID; @@ -31,6 +44,16 @@ export const WiredConditionFurniHasAvatarOnView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } - footer={ } /> + footer={ }> +
+ { LocalizeText('wiredfurni.params.requireall') } + { [ 0, 1 ].map(value => ( + + )) } +
+ ); }; diff --git a/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx b/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx index ac62b15..1b5f413 100644 --- a/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx +++ b/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx @@ -1,36 +1,240 @@ -import { FC, useEffect, useState } from 'react'; -import { WiredFurniType } from '../../../../api'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; +import { Button, Text } from '../../../../common'; import { useWired } from '../../../../hooks'; +import { FURNI_SOURCES, WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector'; import { WiredConditionBaseView } from './WiredConditionBaseView'; -import { WiredSourcesSelector } from '../WiredSourcesSelector'; -export const WiredConditionFurniIsOfTypeView: FC<{}> = props => +const SOURCE_TRIGGER = 0; +const SOURCE_SELECTED = 100; +const SOURCE_SECONDARY_SELECTED = 101; +const FURNI_DELIMITER = ';'; + +const MATCH_FURNI_SOURCES: WiredSourceOption[] = [ + ...FURNI_SOURCES, + { value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' } +]; + +type SelectionMode = 'primary' | 'secondary'; + +const parseIds = (data: string): number[] => { - const { trigger = null, setIntParams = null } = useWired(); - const [ furniSource, setFurniSource ] = useState(() => + if(!data || !data.length) return []; + + const ids = new Set(); + + for(const part of data.split(/[;,\t]/)) { - if(trigger?.intData?.length >= 1) return trigger.intData[0]; - return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; - }); + 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 serializeIds = (ids: number[]): string => +{ + if(!ids || !ids.length) return ''; + + return ids.filter(id => (id > 0)).join(FURNI_DELIMITER); +}; + +interface WiredConditionFurniIsOfTypeViewProps +{ + negative?: boolean; +} + +export const WiredConditionFurniIsOfTypeView: FC = ({ negative = false }) => +{ + const [ matchSource, setMatchSource ] = useState(SOURCE_TRIGGER); + const [ compareSource, setCompareSource ] = useState(SOURCE_TRIGGER); + const [ quantifier, setQuantifier ] = useState(0); + const [ primaryFurniIds, setPrimaryFurniIds ] = useState([]); + const [ secondaryFurniIds, setSecondaryFurniIds ] = useState([]); + const [ selectionMode, setSelectionMode ] = useState('primary'); + + const highlightedIds = useRef([]); + + const { trigger = null, furniIds = [], setFurniIds, setIntParams, setStringParam, setAllowsFurni } = useWired(); + + const syncHighlights = useCallback((nextPrimaryIds: number[], nextSecondaryIds: number[]) => + { + if(highlightedIds.current.length) + { + WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current); + WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current); + } + + const secondarySet = new Set(nextSecondaryIds); + const primaryOnlyIds = nextPrimaryIds.filter(id => !secondarySet.has(id)); + + if(primaryOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(primaryOnlyIds); + if(nextSecondaryIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextSecondaryIds); + + highlightedIds.current = Array.from(new Set([ ...nextPrimaryIds, ...nextSecondaryIds ])); + }, []); + + const switchSelection = useCallback((mode: SelectionMode) => + { + const canEditPrimary = (matchSource === SOURCE_SELECTED) || (compareSource === SOURCE_SELECTED); + const canEditSecondary = (matchSource === SOURCE_SECONDARY_SELECTED) || (compareSource === SOURCE_SECONDARY_SELECTED); + + if(mode === 'primary' && !canEditPrimary) return; + if(mode === 'secondary' && !canEditSecondary) return; + + setSelectionMode(mode); + setFurniIds([ ...(mode === 'primary' ? primaryFurniIds : secondaryFurniIds) ]); + }, [ matchSource, compareSource, primaryFurniIds, secondaryFurniIds, setFurniIds ]); useEffect(() => { if(!trigger) return; - if(trigger.intData.length >= 1) setFurniSource(trigger.intData[0]); - else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); - }, [ trigger ]); - const onChangeFurniSource = (next: number) => setFurniSource(next); + const nextPrimaryIds = trigger.selectedItems ?? []; + const nextSecondaryIds = parseIds(trigger.stringData); + const nextMatchSource = (trigger.intData.length >= 1) + ? trigger.intData[0] + : (nextPrimaryIds.length ? SOURCE_SELECTED : SOURCE_TRIGGER); + const nextCompareSource = (trigger.intData.length >= 2) + ? trigger.intData[1] + : (nextSecondaryIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER); + const nextQuantifier = (trigger.intData.length >= 3) + ? (trigger.intData[2] === 1 ? 1 : 0) + : 0; - const save = () => setIntParams([ furniSource ]); + setMatchSource(nextMatchSource); + setCompareSource(nextCompareSource); + setQuantifier(nextQuantifier); + setPrimaryFurniIds(nextPrimaryIds); + setSecondaryFurniIds(nextSecondaryIds); + setSelectionMode('primary'); + setFurniIds([ ...nextPrimaryIds ]); + }, [ trigger, setFurniIds, negative ]); - const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE; + useEffect(() => + { + if(selectionMode === 'primary') setPrimaryFurniIds(furniIds); + else setSecondaryFurniIds(furniIds); + }, [ furniIds, selectionMode ]); + + useEffect(() => + { + syncHighlights(primaryFurniIds, secondaryFurniIds); + }, [ primaryFurniIds, secondaryFurniIds, syncHighlights ]); + + useEffect(() => + { + const canEditPrimary = (matchSource === SOURCE_SELECTED) || (compareSource === SOURCE_SELECTED); + const canEditSecondary = (matchSource === SOURCE_SECONDARY_SELECTED) || (compareSource === SOURCE_SECONDARY_SELECTED); + + if(selectionMode === 'primary' && !canEditPrimary && canEditSecondary) + { + switchSelection('secondary'); + return; + } + + if(selectionMode === 'secondary' && !canEditSecondary && canEditPrimary) + { + switchSelection('primary'); + return; + } + + const canEditCurrent = ((selectionMode === 'primary') ? canEditPrimary : canEditSecondary); + + setAllowsFurni(canEditCurrent ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE : WiredFurniType.STUFF_SELECTION_OPTION_NONE); + }, [ selectionMode, matchSource, compareSource, switchSelection, setAllowsFurni ]); + + useEffect(() => + { + return () => + { + if(!highlightedIds.current.length) return; + + WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current); + WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current); + highlightedIds.current = []; + }; + }, []); + + const save = useCallback(() => + { + if(selectionMode === 'secondary') + { + setSelectionMode('primary'); + setFurniIds([ ...primaryFurniIds ]); + } + + setIntParams([ + matchSource, + compareSource, + quantifier + ]); + setStringParam(serializeIds(secondaryFurniIds)); + }, [ selectionMode, primaryFurniIds, matchSource, compareSource, quantifier, secondaryFurniIds, setFurniIds, setIntParams, setStringParam ]); + + const selectionLimit = trigger?.maximumItemSelectionCount ?? 0; + const quantifierKeyPrefix = negative ? 'wiredfurni.params.quantifier.furni.neg' : 'wiredfurni.params.quantifier.furni'; return ( } /> + footer={ +
+ +
+ +
+ }> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => ( + + )) } +
+
+
+ { LocalizeText('wiredfurni.params.sources.furni.title.match.0') } +
+ + { selectionLimit ? `${ primaryFurniIds.length }/${ selectionLimit }` : primaryFurniIds.length } +
+
+
+ { LocalizeText('wiredfurni.params.sources.furni.title.match.1') } +
+ + { selectionLimit ? `${ secondaryFurniIds.length }/${ selectionLimit }` : secondaryFurniIds.length } +
+
+
+
); }; diff --git a/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx b/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx index 3bedd0c..4e7935d 100644 --- a/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx +++ b/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx @@ -5,27 +5,38 @@ import { useWired } from '../../../../hooks'; import { WiredConditionBaseView } from './WiredConditionBaseView'; import { WiredSourcesSelector } from '../WiredSourcesSelector'; -export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props => +interface WiredConditionFurniMatchesSnapshotViewProps +{ + negative?: boolean; +} + +export const WiredConditionFurniMatchesSnapshotView: FC = ({ negative = false }) => { const [ stateFlag, setStateFlag ] = useState(0); const [ directionFlag, setDirectionFlag ] = useState(0); const [ positionFlag, setPositionFlag ] = useState(0); + const [ altitudeFlag, setAltitudeFlag ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); const { trigger = null, setIntParams = null } = useWired(); const [ furniSource, setFurniSource ] = useState(() => { + if(trigger?.intData?.length > 4) return trigger.intData[4]; if(trigger?.intData?.length > 3) return trigger.intData[3]; return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; }); - const save = () => setIntParams([ stateFlag, directionFlag, positionFlag, furniSource ]); + const save = () => setIntParams([ stateFlag, directionFlag, positionFlag, altitudeFlag, furniSource, quantifier ]); useEffect(() => { setStateFlag(trigger.getBoolean(0) ? 1 : 0); setDirectionFlag(trigger.getBoolean(1) ? 1 : 0); setPositionFlag(trigger.getBoolean(2) ? 1 : 0); - if(trigger.intData.length > 3) setFurniSource(trigger.intData[3]); + setAltitudeFlag((trigger.intData.length > 4 && trigger.getBoolean(3)) ? 1 : 0); + if(trigger.intData.length > 4) setFurniSource(trigger.intData[4]); + else if(trigger.intData.length > 3) setFurniSource(trigger.intData[3]); else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0); + setQuantifier((trigger.intData.length > 5 && trigger.intData[5] === 1) ? 1 : 0); }, [ trigger ]); const onChangeFurniSource = (next: number) => setFurniSource(next); @@ -37,7 +48,23 @@ export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props => hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } - footer={ }> + footer={ +
+
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( + + ); + }) } +
+ +
+ }>
{ LocalizeText('wiredfurni.params.conditions') }
@@ -52,6 +79,10 @@ export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props => setPositionFlag(event.target.checked ? 1 : 0) } /> { LocalizeText('wiredfurni.params.condition.position') }
+
+ setAltitudeFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.altitude') } +
); diff --git a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx index 7783540..0839d67 100644 --- a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx +++ b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx @@ -1,15 +1,17 @@ import { WiredConditionlayout } from '../../../../api'; import { WiredConditionActorHasHandItemView } from './WiredConditionActorHasHandItem'; +import { WiredConditionActorDirView } from './WiredConditionActorDirView'; import { WiredConditionActorIsGroupMemberView } from './WiredConditionActorIsGroupMemberView'; import { WiredConditionActorIsOnFurniView } from './WiredConditionActorIsOnFurniView'; import { WiredConditionActorIsTeamMemberView } from './WiredConditionActorIsTeamMemberView'; import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWearingBadgeView'; import { WiredConditionActorIsWearingEffectView } from './WiredConditionActorIsWearingEffectView'; -// import { WiredConditionCounterTimeMatchesView } from './WiredConditionCounterTimeMatchesView'; +import { WiredConditionCounterTimeMatchesView } from './WiredConditionCounterTimeMatchesView'; import { WiredConditionDateRangeView } from './WiredConditionDateRangeView'; import { WiredConditionMatchDateView } from './WiredConditionMatchDateView'; import { WiredConditionMatchTimeView } from './WiredConditionMatchTimeView'; import { WiredConditionHasAltitudeView } from './WiredConditionHasAltitudeView'; +import { WiredConditionMovementValidationView } from './WiredConditionMovementValidationView'; import { WiredConditionFurniHasAvatarOnView } from './WiredConditionFurniHasAvatarOnView'; import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurniOnView'; import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView'; @@ -22,51 +24,68 @@ import { WiredConditionTeamHasScoreView } from './WiredConditionTeamHasScoreView import { WiredConditionTriggererMatchView } from './WiredConditionTriggererMatchView'; import { WiredConditionUserPerformsActionView } from './WiredConditionUserPerformsActionView'; import { WiredConditionUserCountInRoomView } from './WiredConditionUserCountInRoomView'; +import { WiredConditionSelectionQuantityView } from './WiredConditionSelectionQuantityView'; export const WiredConditionLayoutView = (code: number) => { switch(code) { case WiredConditionlayout.ACTOR_HAS_HANDITEM: - case WiredConditionlayout.NOT_ACTOR_HAS_HANDITEM: return ; + case WiredConditionlayout.NOT_ACTOR_HAS_HANDITEM: + return ; + case WiredConditionlayout.ACTOR_DIR: + return ; + case WiredConditionlayout.SLC_QUANTITY: + return ; case WiredConditionlayout.TRIGGERER_MATCH: - case WiredConditionlayout.NOT_TRIGGERER_MATCH: return ; + case WiredConditionlayout.NOT_TRIGGERER_MATCH: + return ; case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER: - case WiredConditionlayout.NOT_ACTOR_IN_GROUP: return ; + case WiredConditionlayout.NOT_ACTOR_IN_GROUP: + return ; case WiredConditionlayout.ACTOR_IS_ON_FURNI: - case WiredConditionlayout.NOT_ACTOR_ON_FURNI: return ; + case WiredConditionlayout.NOT_ACTOR_ON_FURNI: + return ; case WiredConditionlayout.ACTOR_IS_IN_TEAM: - case WiredConditionlayout.NOT_ACTOR_IN_TEAM: return ; + case WiredConditionlayout.NOT_ACTOR_IN_TEAM: + return ; case WiredConditionlayout.ACTOR_IS_WEARING_BADGE: - case WiredConditionlayout.NOT_ACTOR_WEARS_BADGE: return ; + case WiredConditionlayout.NOT_ACTOR_WEARS_BADGE: + return ; case WiredConditionlayout.ACTOR_IS_WEARING_EFFECT: - case WiredConditionlayout.NOT_ACTOR_WEARING_EFFECT: return ; + case WiredConditionlayout.NOT_ACTOR_WEARING_EFFECT: + return ; case WiredConditionlayout.DATE_RANGE_ACTIVE: return ; + case WiredConditionlayout.MOVEMENT_VALIDATION: + return ; case WiredConditionlayout.MATCH_TIME: return ; case WiredConditionlayout.MATCH_DATE: return ; case WiredConditionlayout.FURNIS_HAVE_AVATARS: - case WiredConditionlayout.FURNI_NOT_HAVE_HABBO: return ; + case WiredConditionlayout.FURNI_NOT_HAVE_HABBO: + return ; case WiredConditionlayout.HAS_STACKED_FURNIS: return ; case WiredConditionlayout.NOT_HAS_STACKED_FURNIS: return ; case WiredConditionlayout.STUFF_TYPE_MATCHES: - case WiredConditionlayout.NOT_FURNI_IS_OF_TYPE: return ; + case WiredConditionlayout.NOT_FURNI_IS_OF_TYPE: + return ; case WiredConditionlayout.STATES_MATCH: - case WiredConditionlayout.NOT_STATES_MATCH: return ; + case WiredConditionlayout.NOT_STATES_MATCH: + return ; case WiredConditionlayout.TIME_ELAPSED_LESS: return ; case WiredConditionlayout.TIME_ELAPSED_MORE: diff --git a/src/components/wired/views/conditions/WiredConditionMovementValidationView.tsx b/src/components/wired/views/conditions/WiredConditionMovementValidationView.tsx new file mode 100644 index 0000000..03ca6e8 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionMovementValidationView.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionMovementValidationView: FC<{}> = () => +{ + const { setIntParams = null, setStringParam = null } = useWired(); + + const save = () => + { + setIntParams([]); + setStringParam(''); + }; + + return ( + + ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionSelectionQuantityView.tsx b/src/components/wired/views/conditions/WiredConditionSelectionQuantityView.tsx new file mode 100644 index 0000000..11d259c --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionSelectionQuantityView.tsx @@ -0,0 +1,178 @@ +import { FC, useEffect, useMemo, useState } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Button, Slider, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const SOURCE_GROUP_USERS = 0; +const SOURCE_GROUP_FURNI = 1; +const COMPARISON_OPTIONS = [ 0, 1, 2 ]; +const MIN_QUANTITY = 0; +const MAX_QUANTITY = 100; +const QUANTITY_PATTERN = /^\d*$/; + +const USER_SOURCES = [ + { value: 0, label: 'wiredfurni.params.sources.users.0' }, + { value: 200, label: 'wiredfurni.params.sources.users.200' }, + { value: 201, label: 'wiredfurni.params.sources.users.201' } +]; + +const FURNI_SOURCES = [ + { value: 0, label: 'wiredfurni.params.sources.furni.0' }, + { value: 200, label: 'wiredfurni.params.sources.furni.200' }, + { value: 201, label: 'wiredfurni.params.sources.furni.201' } +]; + +const clampQuantity = (value: number) => +{ + if(isNaN(value)) return MIN_QUANTITY; + + return Math.max(MIN_QUANTITY, Math.min(MAX_QUANTITY, Math.floor(value))); +}; + +const normalizeSource = (value: number, allowed: number[]) => +{ + return allowed.includes(value) ? value : 0; +}; + +export const WiredConditionSelectionQuantityView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + const [ comparison, setComparison ] = useState(1); + const [ quantity, setQuantity ] = useState(0); + const [ quantityInput, setQuantityInput ] = useState('0'); + const [ sourceGroup, setSourceGroup ] = useState(SOURCE_GROUP_USERS); + const [ userSource, setUserSource ] = useState(0); + const [ furniSource, setFurniSource ] = useState(0); + + useEffect(() => + { + if(!trigger) return; + + const nextComparison = (trigger.intData.length > 0) ? trigger.intData[0] : 1; + const nextQuantity = clampQuantity((trigger.intData.length > 1) ? trigger.intData[1] : 0); + const nextSourceGroup = (trigger.intData.length > 2 && trigger.intData[2] === SOURCE_GROUP_FURNI) ? SOURCE_GROUP_FURNI : SOURCE_GROUP_USERS; + const nextSourceType = (trigger.intData.length > 3) ? trigger.intData[3] : 0; + + setComparison(COMPARISON_OPTIONS.includes(nextComparison) ? nextComparison : 1); + setQuantity(nextQuantity); + setQuantityInput(nextQuantity.toString()); + setSourceGroup(nextSourceGroup); + setUserSource(nextSourceGroup === SOURCE_GROUP_USERS ? normalizeSource(nextSourceType, USER_SOURCES.map(source => source.value)) : 0); + setFurniSource(nextSourceGroup === SOURCE_GROUP_FURNI ? normalizeSource(nextSourceType, FURNI_SOURCES.map(source => source.value)) : 0); + }, [ trigger ]); + + const activeSources = useMemo(() => (sourceGroup === SOURCE_GROUP_FURNI) ? FURNI_SOURCES : USER_SOURCES, [ sourceGroup ]); + const activeSource = (sourceGroup === SOURCE_GROUP_FURNI) ? furniSource : userSource; + const activeSourceIndex = Math.max(0, activeSources.findIndex(source => source.value === activeSource)); + + const updateQuantity = (value: number) => + { + const nextValue = clampQuantity(value); + + setQuantity(nextValue); + setQuantityInput(nextValue.toString()); + }; + + const updateQuantityInput = (value: string) => + { + if(!QUANTITY_PATTERN.test(value)) return; + + setQuantityInput(value); + + if(!value.length) + { + setQuantity(0); + return; + } + + updateQuantity(parseInt(value)); + }; + + const cycleSource = (direction: -1 | 1) => + { + const nextIndex = (activeSourceIndex + direction + activeSources.length) % activeSources.length; + const nextValue = activeSources[nextIndex].value; + + if(sourceGroup === SOURCE_GROUP_FURNI) setFurniSource(nextValue); + else setUserSource(nextValue); + }; + + const save = () => + { + setIntParams([ + comparison, + clampQuantity(quantity), + sourceGroup, + (sourceGroup === SOURCE_GROUP_FURNI) ? furniSource : userSource + ]); + setStringParam(''); + }; + + return ( + +
+ { LocalizeText('wiredfurni.params.comparison_selection') } + { COMPARISON_OPTIONS.map(value => + { + return ( + + ); + }) } +
+
+ setQuantityInput(clampQuantity(quantity).toString()) } + onChange={ event => updateQuantityInput(event.target.value) } /> + updateQuantity(event as number) } /> + { quantity } +
+
+ { LocalizeText('wiredfurni.params.sources.merged.title') } +
+ + +
+
+ +
+ { LocalizeText(activeSources[activeSourceIndex].label) } +
+ +
+
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx b/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx index d66403b..d8bdfa4 100644 --- a/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx +++ b/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx @@ -24,7 +24,12 @@ const COMPARE_USER_SOURCES: WiredSourceOption[] = [ { value: SOURCE_SPECIFIED_USERNAME, label: 'wiredfurni.params.sources.users.101' } ]; -export const WiredConditionTriggererMatchView: FC<{}> = () => +interface WiredConditionTriggererMatchViewProps +{ + negative?: boolean; +} + +export const WiredConditionTriggererMatchView: FC = ({ negative = false }) => { const [ entityType, setEntityType ] = useState(ENTITY_HABBO); const [ avatarMode, setAvatarMode ] = useState(AVATAR_MODE_ANY); @@ -36,6 +41,7 @@ export const WiredConditionTriggererMatchView: FC<{}> = () => const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); const needsUsername = (avatarMode === AVATAR_MODE_CERTAIN) || (compareUserSource === SOURCE_SPECIFIED_USERNAME); + const quantifierKeyPrefix = negative ? 'wiredfurni.params.quantifier.users.neg' : 'wiredfurni.params.quantifier.users'; const save = () => { @@ -81,7 +87,7 @@ export const WiredConditionTriggererMatchView: FC<{}> = () => return (
setQuantifier(value) } /> - { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) } + { LocalizeText(`${ quantifierKeyPrefix }.${ value }`) }
); }) } diff --git a/src/components/wired/views/extras/WiredExtraBaseView.tsx b/src/components/wired/views/extras/WiredExtraBaseView.tsx new file mode 100644 index 0000000..22b5394 --- /dev/null +++ b/src/components/wired/views/extras/WiredExtraBaseView.tsx @@ -0,0 +1,32 @@ +import { WiredActionDefinition } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, PropsWithChildren, ReactNode, useEffect } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { useWired } from '../../../../hooks'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredExtraBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; + validate?: () => boolean; + cardStyle?: CSSProperties; + footer?: ReactNode; +} + +export const WiredExtraBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, validate = null, hasSpecialInput = false, children = null, cardStyle = undefined, footer = null } = props; + const { trigger = null, setActionDelay = null } = useWired(); + + useEffect(() => + { + setActionDelay((trigger as WiredActionDefinition)?.delayInPulses ?? 0); + }, [ trigger, setActionDelay ]); + + return ( + + { children } + + ); +}; diff --git a/src/components/wired/views/extras/WiredExtraFilterFurniView.tsx b/src/components/wired/views/extras/WiredExtraFilterFurniView.tsx new file mode 100644 index 0000000..687f368 --- /dev/null +++ b/src/components/wired/views/extras/WiredExtraFilterFurniView.tsx @@ -0,0 +1,48 @@ +import { ChangeEvent, FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredExtraBaseView } from './WiredExtraBaseView'; + +const MIN_FILTER = 0; +const MAX_FILTER = 10000; + +const clampValue = (value: number) => +{ + if(isNaN(value)) return MIN_FILTER; + + return Math.max(MIN_FILTER, Math.min(MAX_FILTER, Math.floor(value))); +}; + +export const WiredExtraFilterFurniView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + const [ amount, setAmount ] = useState(0); + + useEffect(() => + { + if(!trigger) return; + + setAmount(clampValue((trigger.intData.length > 0) ? trigger.intData[0] : 0)); + }, [ trigger ]); + + const onChange = (event: ChangeEvent) => + { + setAmount(clampValue(Number(event.target.value))); + }; + + const save = () => + { + setIntParams([ clampValue(amount) ]); + setStringParam(''); + }; + + return ( + +
+ { LocalizeText('wiredfurni.params.setfilter') } + +
+
+ ); +}; diff --git a/src/components/wired/views/extras/WiredExtraFilterUserView.tsx b/src/components/wired/views/extras/WiredExtraFilterUserView.tsx new file mode 100644 index 0000000..345c708 --- /dev/null +++ b/src/components/wired/views/extras/WiredExtraFilterUserView.tsx @@ -0,0 +1,48 @@ +import { ChangeEvent, FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredExtraBaseView } from './WiredExtraBaseView'; + +const MIN_FILTER = 0; +const MAX_FILTER = 10000; + +const clampValue = (value: number) => +{ + if(isNaN(value)) return MIN_FILTER; + + return Math.max(MIN_FILTER, Math.min(MAX_FILTER, Math.floor(value))); +}; + +export const WiredExtraFilterUserView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + const [ amount, setAmount ] = useState(0); + + useEffect(() => + { + if(!trigger) return; + + setAmount(clampValue((trigger.intData.length > 0) ? trigger.intData[0] : 0)); + }, [ trigger ]); + + const onChange = (event: ChangeEvent) => + { + setAmount(clampValue(Number(event.target.value))); + }; + + const save = () => + { + setIntParams([ clampValue(amount) ]); + setStringParam(''); + }; + + return ( + +
+ { LocalizeText('wiredfurni.params.setfilter') } + +
+
+ ); +}; diff --git a/src/components/wired/views/selectors/WiredActionFurniAreaView.tsx b/src/components/wired/views/selectors/WiredActionFurniAreaView.tsx index 3b8d759..9eb8777 100644 --- a/src/components/wired/views/selectors/WiredActionFurniAreaView.tsx +++ b/src/components/wired/views/selectors/WiredActionFurniAreaView.tsx @@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useState } from 'react'; import { LocalizeText } from '../../../../api'; import { Button, Text } from '../../../../common'; import { useWired } from '../../../../hooks'; -import { WiredActionBaseView } from '../actions/WiredActionBaseView'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; export const WiredActionFurniAreaView: FC<{}> = props => { @@ -101,7 +101,7 @@ export const WiredActionFurniAreaView: FC<{}> = props => : 0; return ( - +
{ LocalizeText('wiredfurni.params.area_selection') } { LocalizeText('wiredfurni.params.area_selection.info') } @@ -154,6 +154,6 @@ export const WiredActionFurniAreaView: FC<{}> = props => { LocalizeText('wiredfurni.params.selector_option.1') }
-
+ ); }; diff --git a/src/components/wired/views/selectors/WiredSelectorBaseView.tsx b/src/components/wired/views/selectors/WiredSelectorBaseView.tsx new file mode 100644 index 0000000..ab12ba4 --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorBaseView.tsx @@ -0,0 +1,25 @@ +import { CSSProperties, FC, PropsWithChildren, ReactNode } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredSelectorBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; + validate?: () => boolean; + cardStyle?: CSSProperties; + hideDelay?: boolean; + footer?: ReactNode; +} + +export const WiredSelectorBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, validate = null, hasSpecialInput = false, children = null, cardStyle = undefined, footer = null } = props; + + return ( + + { children } + + ); +}; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniAltitudeView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniAltitudeView.tsx new file mode 100644 index 0000000..d9bc0cb --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorFurniAltitudeView.tsx @@ -0,0 +1,165 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Slider, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; + +const MIN_ALTITUDE = 0; +const MAX_ALTITUDE = 40; +const ALTITUDE_STEP = 0.5; +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); +}; + +export const WiredSelectorFurniAltitudeView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + const [ comparison, setComparison ] = useState(1); + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const [ altitude, setAltitude ] = useState(0); + const [ altitudeInput, setAltitudeInput ] = useState('0'); + + useEffect(() => + { + if(!trigger) return; + + const nextAltitude = parseAltitude(trigger.stringData); + + setComparison((trigger.intData.length > 0) ? trigger.intData[0] : 1); + setFilterExisting((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false); + setInvert((trigger.intData.length > 2) ? (trigger.intData[2] === 1) : false); + 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 = useCallback(() => + { + setIntParams([ + comparison, + filterExisting ? 1 : 0, + invert ? 1 : 0 + ]); + setStringParam(formatAltitude(altitude)); + }, [ altitude, comparison, filterExisting, invert, setIntParams, setStringParam ]); + + return ( + +
+
+ { [ 0, 1, 2 ].map(value => + { + return ( + + ); + }) } +
+ +
+ { LocalizeText('wiredfurni.params.setaltitude') } + setAltitudeInput(formatAltitude(altitude)) } + onChange={ event => updateAltitudeInput(event.target.value) } /> +
+ +
+ updateAltitude(event as number) } /> + { formatAltitude(altitude) } +
+ +
+ + { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + +
+
+ ); +}; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx index cc0f338..d39b4df 100644 --- a/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx +++ b/src/components/wired/views/selectors/WiredSelectorFurniByTypeView.tsx @@ -2,7 +2,7 @@ import { FC, useCallback, useEffect, useState } from 'react'; import { LocalizeText, WiredFurniType } from '../../../../api'; import { Text } from '../../../../common'; import { useWired } from '../../../../hooks'; -import { WiredActionBaseView } from '../actions/WiredActionBaseView'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; const SOURCE_FURNI_PICKED = 0; @@ -42,7 +42,7 @@ export const WiredSelectorFurniByTypeView: FC<{}> = () => const requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_BY_ID; return ( - +
-
+ ); }; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniNeighborhoodView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniNeighborhoodView.tsx index 27bf7d6..a4617e6 100644 --- a/src/components/wired/views/selectors/WiredSelectorFurniNeighborhoodView.tsx +++ b/src/components/wired/views/selectors/WiredSelectorFurniNeighborhoodView.tsx @@ -1,11 +1,11 @@ -import { GetRoomEngine, RoomObjectCategory } from '@nitrots/nitro-renderer'; -import { CSSProperties, FC, useCallback, useEffect, useState } from 'react'; +import { GetRoomEngine } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, MouseEvent as ReactMouseEvent, useCallback, useEffect, useState } from 'react'; import { FaMinus, FaPlus, FaTimes } from 'react-icons/fa'; import { MdGridOn } from 'react-icons/md'; -import { GetRoomSession, LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; +import { LocalizeText, WiredFurniType } from '../../../../api'; import { Button, Text } from '../../../../common'; import { useWired } from '../../../../hooks'; -import { WiredActionBaseView } from '../actions/WiredActionBaseView'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; const SOURCE_USER_TRIGGER = 0; const SOURCE_USER_SIGNAL = 1; @@ -47,33 +47,74 @@ const tileTop = (rx: number, ry: number) => interface GridProps { selectedTiles: Tile[]; + targetTile: Tile; invert: boolean; - onToggle: (x: number, y: number) => void; + onSetTile: (x: number, y: number, selected: boolean) => void; + onMoveTarget: (x: number, y: number) => void; + targetPlacementMode: boolean; } -const NeighborhoodGrid: FC = ({ selectedTiles, invert, onToggle }) => +const NeighborhoodGrid: FC = ({ selectedTiles, targetTile, invert, onSetTile, onMoveTarget, targetPlacementMode }) => { + const [ dragMode, setDragMode ] = useState<'add' | 'remove' | 'target' | null>(null); const tiles: JSX.Element[] = []; + useEffect(() => + { + const stopDragging = () => setDragMode(null); + + window.addEventListener('mouseup', stopDragging); + + return () => window.removeEventListener('mouseup', stopDragging); + }, []); + + const beginTileDrag = (event: ReactMouseEvent, rx: number, ry: number, isTarget: boolean, isSelected: boolean) => + { + event.preventDefault(); + + if(targetPlacementMode) + { + setDragMode('target'); + onMoveTarget(rx, ry); + return; + } + + const nextMode = isSelected ? 'remove' : 'add'; + + setDragMode(nextMode); + onSetTile(rx, ry, nextMode === 'add'); + }; + + const continueTileDrag = (event: ReactMouseEvent, rx: number, ry: number, isTarget: boolean) => + { + if(!(event.buttons & 1) || !dragMode) return; + + if(dragMode === 'target') + { + onMoveTarget(rx, ry); + return; + } + + onSetTile(rx, ry, dragMode === 'add'); + }; + for (let ry = -GRID_RANGE; ry <= GRID_RANGE; ry++) { for (let rx = -GRID_RANGE; rx <= GRID_RANGE; rx++) { - const isCenter = rx === 0 && ry === 0; + const isTarget = rx === targetTile.x && ry === targetTile.y; const isSelected = tileIncluded(selectedTiles, rx, ry); const isActive = invert ? !isSelected : isSelected; const left = tileLeft(rx, ry); const top_ = tileTop(rx, ry); const zIdx = rx + ry + GRID_RANGE * 2 + 10; - const bgColor = isCenter - ? '#ff9500' - : isActive - ? '#3399ff' - : '#2a3042'; + const bgColor = isActive + ? '#3399ff' + : '#2a3042'; - const borderColor = isCenter - ? '#cc6600' + const borderColor = isTarget + ? '#ffffff' : isActive ? '#1166cc' : '#1a2032'; @@ -86,36 +127,61 @@ const NeighborhoodGrid: FC = ({ selectedTiles, invert, onToggle }) => top: top_, clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)', backgroundColor: bgColor, - cursor: isCenter ? 'default' : 'pointer', + cursor: 'pointer', zIndex: zIdx, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: isTarget ? '#ffffff' : 'transparent', + fontSize: 10 }; const border: CSSProperties = { position: 'absolute', - width: TILE_W + 2, - height: TILE_H + 2, - left: left - 1, - top: top_ - 1, + width: TILE_W + (isTarget ? 6 : 2), + height: TILE_H + (isTarget ? 6 : 2), + left: left - (isTarget ? 3 : 1), + top: top_ - (isTarget ? 3 : 1), clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)', backgroundColor: borderColor, zIndex: zIdx - 1, pointerEvents: 'none', }; + const targetOutline: CSSProperties = { + position: 'absolute', + width: TILE_W + 4, + height: TILE_H + 4, + left: left - 2, + top: top_ - 2, + zIndex: zIdx + 1, + pointerEvents: 'none', + overflow: 'visible' + }; + tiles.push(
, + isTarget && ( + + + + ),
!isCenter && onToggle(rx, ry) } - />, + onMouseDown={ event => beginTileDrag(event, rx, ry, isTarget, isSelected) } + onMouseEnter={ event => continueTileDrag(event, rx, ry, isTarget) } />, ); } } return ( -
+
event.preventDefault() }> { tiles }
); @@ -127,10 +193,18 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () => const [ filterExisting, setFilterExisting ] = useState(false); const [ invert, setInvert ] = useState(false); const [ sourceType, setSourceType ] = useState(SOURCE_USER_TRIGGER); + const [ targetTile, setTargetTile ] = useState({ x: 0, y: 0 }); + const [ targetPlacementMode, setTargetPlacementMode ] = useState(false); const [ curX, setCurX ] = useState(0); const [ curY, setCurY ] = useState(0); - const { trigger = null, furniIds = [], setIntParams, setFurniIds } = useWired(); + const { trigger = null, furniIds = [], setIntParams } = useWired(); + + useEffect(() => + { + GetRoomEngine().areaSelectionManager.clearHighlight(); + GetRoomEngine().areaSelectionManager.deactivate(); + }, []); useEffect(() => { @@ -140,15 +214,17 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () => if(p.length >= 1) setSourceType(p[0]); if(p.length >= 2) setFilterExisting(p[1] === 1); if(p.length >= 3) setInvert(p[2] === 1); + if(p.length >= 5) setTargetTile({ x: p[3], y: p[4] }); + else setTargetTile({ x: 0, y: 0 }); - if(p.length >= 4) + if(p.length >= 6) { - const n = p[3]; + const n = p[5]; const tiles: Tile[] = []; for(let i = 0; i < n; i++) { - const xi = 4 + i * 2; + const xi = 6 + i * 2; if(xi + 1 < p.length) tiles.push({ x: p[xi], y: p[xi + 1] }); } @@ -160,71 +236,47 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () => } }, [ trigger ]); - useEffect(() => - { - if(sourceType !== SOURCE_FURNI_PICKED || !trigger) return; - - const roomId = GetRoomSession().roomId; - const wiredObj = GetRoomEngine().getRoomObject(roomId, trigger.id, RoomObjectCategory.FLOOR); - - if(!wiredObj) return; - - const wiredPos = wiredObj.getLocation(); - const tileSet = new Set(selectedTiles.map(t => `${ t.x },${ t.y }`)); - const limit = trigger.maximumItemSelectionCount; - - const allFloorObjects = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR); - const newIds: number[] = []; - - for(const obj of allFloorObjects) - { - if(newIds.length >= limit) break; - if(obj.id < 0 || obj.id === trigger.id) continue; - - const pos = obj.getLocation(); - const relX = Math.round(pos.x - wiredPos.x); - const relY = Math.round(pos.y - wiredPos.y); - - const isInTiles = tileSet.has(`${ relX },${ relY }`); - - if(invert ? !isInTiles : isInTiles) newIds.push(obj.id); - } - - setFurniIds(prevValue => - { - if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); - - WiredSelectionVisualizer.applySelectionShaderToFurni(newIds); - - return newIds; - }); - }, [ sourceType, selectedTiles, invert, trigger, setFurniIds ]); - const save = useCallback(() => { const params: number[] = [ sourceType, filterExisting ? 1 : 0, invert ? 1 : 0, + targetTile.x, + targetTile.y, selectedTiles.length, ...selectedTiles.flatMap(t => [ t.x, t.y ]), ]; setIntParams(params); - }, [ sourceType, filterExisting, invert, selectedTiles, setIntParams ]); + }, [ sourceType, filterExisting, invert, selectedTiles, targetTile.x, targetTile.y, setIntParams ]); - const toggleTile = useCallback((x: number, y: number) => + const setTileSelection = useCallback((x: number, y: number, selected: boolean) => { setSelectedTiles(prev => - tileIncluded(prev, x, y) - ? prev.filter(t => !(t.x === x && t.y === y)) - : [ ...prev, { x, y } ] - ); + { + const alreadySelected = tileIncluded(prev, x, y); + + if(selected) + { + if(alreadySelected) return prev; + + return [ ...prev, { x, y } ]; + } + + if(!alreadySelected) return prev; + + return prev.filter(t => !(t.x === x && t.y === y)); + }); + }, []); + + const moveTargetTile = useCallback((x: number, y: number) => + { + setTargetTile({ x, y }); }, []); const addTile = useCallback(() => { - if(curX === 0 && curY === 0) return; if(!tileIncluded(selectedTiles, curX, curY)) setSelectedTiles(prev => [ ...prev, { x: curX, y: curY } ]); }, [ curX, curY, selectedTiles ]); @@ -278,12 +330,23 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () => const pickedLimit = trigger?.maximumItemSelectionCount ?? 20; return ( - +
{ LocalizeText('wiredfurni.params.neighborhood_selection') }
+ @@ -299,7 +362,13 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () =>
- +
@@ -378,6 +447,6 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () => }
- + ); }; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniOnFurniView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniOnFurniView.tsx new file mode 100644 index 0000000..585b70d --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorFurniOnFurniView.tsx @@ -0,0 +1,82 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; + +export const WiredSelectorFurniOnFurniView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null } = useWired(); + const [ selectionType, setSelectionType ] = useState(0); + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const [ furniSource, setFurniSource ] = useState(() => + { + if(trigger?.intData?.length > 1) return trigger.intData[1]; + return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; + }); + + useEffect(() => + { + if(!trigger) return; + + setSelectionType((trigger.intData.length > 0) ? trigger.intData[0] : 0); + setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0)); + setFilterExisting((trigger.intData.length > 2) ? (trigger.intData[2] === 1) : false); + setInvert((trigger.intData.length > 3) ? (trigger.intData[3] === 1) : false); + }, [ trigger ]); + + const save = () => setIntParams([ + selectionType, + furniSource, + filterExisting ? 1 : 0, + invert ? 1 : 0 + ]); + + return ( + }> +
+
+ { LocalizeText('wiredfurni.params.selection_type') } + { [ 0, 1, 2, 3 ].map(value => + { + return ( + + ); + }) } +
+ +
+ + { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + +
+
+ ); +}; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniPicksView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniPicksView.tsx new file mode 100644 index 0000000..3487655 --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorFurniPicksView.tsx @@ -0,0 +1,55 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; + +export const WiredSelectorFurniPicksView: FC<{}> = () => +{ + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const { trigger = null, setIntParams = null } = useWired(); + + useEffect(() => + { + if(!trigger) return; + + const params = trigger.intData; + setFilterExisting(params.length > 0 ? (params[0] === 1) : false); + setInvert(params.length > 1 ? (params[1] === 1) : false); + }, [ trigger ]); + + const save = useCallback(() => + { + setIntParams([ + filterExisting ? 1 : 0, + invert ? 1 : 0 + ]); + }, [ filterExisting, invert, setIntParams ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + +
+
+ ); +}; diff --git a/src/components/wired/views/selectors/WiredSelectorFurniSignalView.tsx b/src/components/wired/views/selectors/WiredSelectorFurniSignalView.tsx new file mode 100644 index 0000000..b822ece --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorFurniSignalView.tsx @@ -0,0 +1,55 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; + +export const WiredSelectorFurniSignalView: FC<{}> = () => +{ + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const { trigger = null, setIntParams = null } = useWired(); + + useEffect(() => + { + if(!trigger) return; + + const params = trigger.intData; + setFilterExisting(params.length > 0 ? (params[0] === 1) : false); + setInvert(params.length > 1 ? (params[1] === 1) : false); + }, [ trigger ]); + + const save = useCallback(() => + { + setIntParams([ + filterExisting ? 1 : 0, + invert ? 1 : 0 + ]); + }, [ filterExisting, invert, setIntParams ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + +
+
+ ); +}; diff --git a/src/components/wired/views/selectors/WiredSelectorUsersAreaView.tsx b/src/components/wired/views/selectors/WiredSelectorUsersAreaView.tsx index d07af98..2ec0d9b 100644 --- a/src/components/wired/views/selectors/WiredSelectorUsersAreaView.tsx +++ b/src/components/wired/views/selectors/WiredSelectorUsersAreaView.tsx @@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useState } from 'react'; import { LocalizeText } from '../../../../api'; import { Button, Text } from '../../../../common'; import { useWired } from '../../../../hooks'; -import { WiredActionBaseView } from '../actions/WiredActionBaseView'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; export const WiredSelectorUsersAreaView: FC<{}> = props => { @@ -13,14 +13,12 @@ export const WiredSelectorUsersAreaView: FC<{}> = props => const [ areaHeight, setAreaHeight ] = useState(0); const [ filterExisting, setFilterExisting ] = useState(false); const [ invert, setInvert ] = useState(false); - const [ excludeBots, setExcludeBots ] = useState(false); - const [ excludePets, setExcludePets ] = useState(false); const { trigger = null, setIntParams } = useWired(); const save = useCallback(() => { - setIntParams([ rootX, rootY, areaWidth, areaHeight, filterExisting ? 1 : 0, invert ? 1 : 0, excludeBots ? 1 : 0, excludePets ? 1 : 0 ]); - }, [ rootX, rootY, areaWidth, areaHeight, filterExisting, invert, excludeBots, excludePets, setIntParams ]); + setIntParams([ rootX, rootY, areaWidth, areaHeight, filterExisting ? 1 : 0, invert ? 1 : 0 ]); + }, [ rootX, rootY, areaWidth, areaHeight, filterExisting, invert, setIntParams ]); useEffect(() => { @@ -76,8 +74,6 @@ export const WiredSelectorUsersAreaView: FC<{}> = props => setFilterExisting(trigger.intData.length >= 5 && trigger.intData[4] === 1); setInvert(trigger.intData.length >= 6 && trigger.intData[5] === 1); - setExcludeBots(trigger.intData.length >= 7 && trigger.intData[6] === 1); - setExcludePets(trigger.intData.length >= 8 && trigger.intData[7] === 1); }, [ trigger ]); useEffect(() => @@ -92,7 +88,7 @@ export const WiredSelectorUsersAreaView: FC<{}> = props => const hasArea = areaWidth > 0 && areaHeight > 0; return ( - +
{ LocalizeText('wiredfurni.params.area_selection') } { LocalizeText('wiredfurni.params.area_selection.info') } @@ -134,25 +130,7 @@ export const WiredSelectorUsersAreaView: FC<{}> = props => onChange={ e => setInvert(e.target.checked) } /> { LocalizeText('wiredfurni.params.selector_option.1') } - - - -
-
+ ); }; diff --git a/src/components/wired/views/selectors/WiredSelectorUsersByActionView.tsx b/src/components/wired/views/selectors/WiredSelectorUsersByActionView.tsx new file mode 100644 index 0000000..496dc87 --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorUsersByActionView.tsx @@ -0,0 +1,142 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; + +const ACTION_WAVE = 1; +const ACTION_BLOW_KISS = 2; +const ACTION_LAUGH = 3; +const ACTION_AWAKE = 4; +const ACTION_RELAX = 5; +const ACTION_SIT = 6; +const ACTION_STAND = 7; +const ACTION_LAY = 8; +const ACTION_SIGN = 9; +const ACTION_DANCE = 10; +const ACTION_THUMB_UP = 11; + +const ACTION_OPTIONS = [ + { value: ACTION_WAVE, label: 'widget.memenu.wave' }, + { value: ACTION_BLOW_KISS, label: 'widget.memenu.blow' }, + { value: ACTION_LAUGH, label: 'widget.memenu.laugh' }, + { value: ACTION_THUMB_UP, label: 'widget.memenu.thumb' }, + { value: ACTION_AWAKE, label: 'wiredfurni.params.action.4' }, + { value: ACTION_RELAX, label: 'avatar.widget.random_walk' }, + { value: ACTION_SIT, label: 'widget.memenu.sit' }, + { value: ACTION_STAND, label: 'widget.memenu.stand' }, + { value: ACTION_LAY, label: 'wiredfurni.params.action.8' }, + { value: ACTION_SIGN, label: 'widget.memenu.sign' }, + { value: ACTION_DANCE, label: 'widget.memenu.dance' } +]; + +const SIGN_OPTIONS = Array.from({ length: 18 }, (_, value) => ({ + value, + label: `wiredfurni.params.action.sign.${ value }` +})); + +const DANCE_OPTIONS = [ + { value: 1, label: 'widget.memenu.dance1' }, + { value: 2, label: 'widget.memenu.dance2' }, + { value: 3, label: 'widget.memenu.dance3' }, + { value: 4, label: 'widget.memenu.dance4' } +]; + +export const WiredSelectorUsersByActionView: FC<{}> = () => +{ + const [ selectedAction, setSelectedAction ] = useState(ACTION_WAVE); + const [ signFilterEnabled, setSignFilterEnabled ] = useState(false); + const [ signId, setSignId ] = useState(0); + const [ danceFilterEnabled, setDanceFilterEnabled ] = useState(false); + const [ danceId, setDanceId ] = useState(1); + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const { trigger = null, setIntParams = null } = useWired(); + + useEffect(() => + { + if(!trigger) return; + + const params = trigger.intData; + + setSelectedAction(params.length > 0 ? params[0] : ACTION_WAVE); + setSignFilterEnabled(params.length > 1 ? (params[1] === 1) : false); + setSignId(params.length > 2 ? params[2] : 0); + setDanceFilterEnabled(params.length > 3 ? (params[3] === 1) : false); + setDanceId(params.length > 4 ? params[4] : 1); + setFilterExisting(params.length > 5 ? (params[5] === 1) : false); + setInvert(params.length > 6 ? (params[6] === 1) : false); + }, [ trigger ]); + + const save = useCallback(() => + { + setIntParams([ + selectedAction, + signFilterEnabled ? 1 : 0, + signId, + danceFilterEnabled ? 1 : 0, + danceId, + filterExisting ? 1 : 0, + invert ? 1 : 0 + ]); + }, [ selectedAction, signFilterEnabled, signId, danceFilterEnabled, danceId, filterExisting, invert, setIntParams ]); + + return ( + +
+
+ Action + +
+ + { (selectedAction === ACTION_SIGN) && +
+ + { signFilterEnabled && + } +
} + + { (selectedAction === ACTION_DANCE) && +
+ + { danceFilterEnabled && + } +
} + +
+ + { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + +
+
+ ); +}; diff --git a/src/components/wired/views/selectors/WiredSelectorUsersByNameView.tsx b/src/components/wired/views/selectors/WiredSelectorUsersByNameView.tsx new file mode 100644 index 0000000..c8ff653 --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorUsersByNameView.tsx @@ -0,0 +1,69 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSelectorBaseView } from './WiredSelectorBaseView'; + +export const WiredSelectorUsersByNameView: FC<{}> = () => +{ + const [ namesText, setNamesText ] = useState(''); + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + + useEffect(() => + { + if(!trigger) return; + + const params = trigger.intData; + + setNamesText(trigger.stringData || ''); + setFilterExisting(params.length > 0 ? (params[0] === 1) : false); + setInvert(params.length > 1 ? (params[1] === 1) : false); + }, [ trigger ]); + + const save = useCallback(() => + { + setStringParam(namesText); + setIntParams([ + filterExisting ? 1 : 0, + invert ? 1 : 0 + ]); + }, [ namesText, filterExisting, invert, setStringParam, setIntParams ]); + + return ( + +
+
+ { LocalizeText('wiredfurni.params.enter_names') } +