@@ -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%"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 244 B |
|
After Width: | Height: | Size: 231 B |
|
After Width: | Height: | Size: 156 B |
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 235 B |
|
After Width: | Height: | Size: 164 B |
|
After Width: | Height: | Size: 154 B |
@@ -6,16 +6,17 @@ import { NitroCardContextProvider } from './NitroCardContext';
|
||||
export interface NitroCardViewProps extends DraggableWindowProps, ColumnProps
|
||||
{
|
||||
theme?: string;
|
||||
isResizable?: boolean;
|
||||
}
|
||||
|
||||
export const NitroCardView: FC<NitroCardViewProps> = 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<HTMLDivElement>();
|
||||
|
||||
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<NitroCardViewProps> = props =>
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
}, [ classNames, isResizable ]);
|
||||
|
||||
return (
|
||||
<NitroCardContextProvider value={ { theme } }>
|
||||
|
||||
@@ -232,7 +232,9 @@ export const DraggableWindow: FC<DraggableWindowProps> = 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}
|
||||
|
||||
@@ -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<RoomWidgetUpdateRentableBotChatEvent>(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>(RoomEngineEvent.NORMAL_MODE, event =>
|
||||
{
|
||||
if(isGameMode) setGameMode(false);
|
||||
@@ -48,6 +65,41 @@ export const AvatarInfoWidgetView: FC<{}> = props =>
|
||||
|
||||
useUiEvent<RoomWidgetUpdateRentableBotChatEvent>(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;
|
||||
|
||||
@@ -61,6 +61,8 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = 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<InfoStandWidgetUserViewProps> = 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<InfoStandWidgetUserViewProps> = 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<InfoStandWidgetUserViewProps> = props =
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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<PropsWithChildren<WiredBaseViewProps>> = props =>
|
||||
const [ needsSave, setNeedsSave ] = useState<boolean>(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<PropsWithChildren<WiredBaseViewProps>> = props =>
|
||||
}
|
||||
}, [ trigger, hasSpecialInput, setIntParams, setStringParam, setFurniIds ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () => clearRoomAreaSelection();
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
@@ -99,8 +111,16 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
|
||||
setAllowsFurni(requiresFurni);
|
||||
}, [ trigger, requiresFurni, setAllowsFurni ]);
|
||||
|
||||
const resolvedCardStyle: CSSProperties = { ...cardStyle };
|
||||
|
||||
if(resolvedCardStyle.width !== undefined)
|
||||
{
|
||||
resolvedCardStyle.minWidth = resolvedCardStyle.width;
|
||||
delete resolvedCardStyle.width;
|
||||
}
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-wired" theme="primary-slim" uniqueKey="nitro-wired" style={ cardStyle }>
|
||||
<NitroCardView className="nitro-wired" theme="primary-slim" uniqueKey="nitro-wired" isResizable={ false } style={ resolvedCardStyle }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('wiredfurni.title') } onCloseClick={ onClose } />
|
||||
<NitroCardContentView>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
@@ -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<number, string> = {
|
||||
0: iconWiredDirN,
|
||||
1: iconWiredDirNe,
|
||||
2: iconWiredDirE,
|
||||
3: iconWiredDirSe,
|
||||
4: iconWiredDirS,
|
||||
5: iconWiredDirSw,
|
||||
6: iconWiredDirW,
|
||||
7: iconWiredDirNw
|
||||
};
|
||||
|
||||
export const WiredDirectionIcon: FC<WiredDirectionIconProps> = props =>
|
||||
{
|
||||
const { direction = 0, selected = false, debugValue = null, iconSrc = null, showImage = true } = props;
|
||||
const icon = iconSrc ?? DIRECTION_ICON_MAP[direction];
|
||||
|
||||
return (
|
||||
<span className="inline-flex flex-col items-center justify-center leading-none">
|
||||
{ showImage &&
|
||||
<img
|
||||
alt=""
|
||||
className={ `h-auto w-auto object-contain ${ selected ? 'brightness-100' : 'opacity-90' }` }
|
||||
draggable={ false }
|
||||
src={ icon } /> }
|
||||
{ (debugValue !== null && debugValue !== undefined) &&
|
||||
<span className={ `${ showImage ? 'mt-[1px]' : '' } text-[9px] text-black` }>{ debugValue }</span> }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -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<WiredHandItemFieldProps> = 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<number>(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0);
|
||||
|
||||
onChange && onChange(copiedHandItem);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText(labelKey) }</Text>
|
||||
<div className="flex items-center gap-2">
|
||||
<select className="form-select form-select-sm flex-1" value={ handItemId } onChange={ event => onChange(parseInt(event.target.value)) }>
|
||||
<option value={ 0 }>------</option>
|
||||
{ options.map(value => <option key={ value } value={ value }>{ getLabel(value) }</option>) }
|
||||
</select>
|
||||
{ showCopyButton && <Button variant="secondary" onClick={ copyOwnHandItem }>{ LocalizeText('wiredfurni.params.capture.handitem') }</Button> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
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 (
|
||||
<WiredActionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
{ OPERATOR_OPTIONS.map(option =>
|
||||
{
|
||||
return (
|
||||
<div key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (operator === option.value) } className="form-check-input" id={ `adjustClockOperator${ option.value }` } name="adjustClockOperator" type="radio" onChange={ () => setOperator(option.value) } />
|
||||
<Text>{ LocalizeText(option.label) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.time.minute_selection') }</Text>
|
||||
<Slider
|
||||
max={ MINUTES_MAX }
|
||||
min={ MINUTES_MIN }
|
||||
step={ 1 }
|
||||
value={ minutes }
|
||||
onChange={ event => setMinutes(normalizeMinutes(event as number)) } />
|
||||
<Text small>{ minutes }</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.time.second_selection') }</Text>
|
||||
<Slider
|
||||
max={ HALF_SECONDS_MAX }
|
||||
min={ HALF_SECONDS_MIN }
|
||||
step={ 1 }
|
||||
value={ halfSeconds }
|
||||
onChange={ event => setHalfSeconds(normalizeHalfSeconds(event as number)) } />
|
||||
<Text small>{ secondsLabel }</Text>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<number>(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 (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } footer={ <WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCES } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> }>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
<div className="flex items-center justify-center">
|
||||
<LayoutAvatarImageView direction={ 4 } figure={ figure } />
|
||||
<Button onClick={ event => setFigure(GetSessionDataManager().figure) }>{ LocalizeText('wiredfurni.params.capture.figure') }</Button>
|
||||
|
||||
@@ -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<number>(100);
|
||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||
{
|
||||
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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCES } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } />
|
||||
</div>
|
||||
}>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (followMode === 1) } className="form-check-input" id="followMode1" name="followMode" type="radio" onChange={ event => setFollowMode(1) } />
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 1) return trigger.intData[1];
|
||||
return 0;
|
||||
});
|
||||
const [ userSource, setUserSource ] = useState<number>(0);
|
||||
const [ botSource, setBotSource ] = useState<number>(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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.handitem') }</Text>
|
||||
<select className="form-select form-select-sm" value={ handItemId } onChange={ event => setHandItemId(parseInt(event.target.value)) }>
|
||||
<option value="0">------</option>
|
||||
{ ALLOWED_HAND_ITEM_IDS.map(value => <option key={ value } value={ value }>{ LocalizeText(`handitem${ value }`) }</option>) }
|
||||
</select>
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } userSources={ USER_SOURCE_OPTIONS } onChangeUsers={ setUserSource } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCE_OPTIONS } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } />
|
||||
</div>
|
||||
}>
|
||||
<div className="form-check">
|
||||
<input checked={ (botSource === 100) } className="form-check-input" id="botGiveHandItemUseNamedBot" type="checkbox" onChange={ event => setBotSource(event.target.checked ? 100 : 0) } />
|
||||
<label className="form-check-label" htmlFor="botGiveHandItemUseNamedBot">{ LocalizeText('wiredfurni.params.bot.usage') }</label>
|
||||
</div>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
<WiredHandItemField handItemId={ handItemId } onChange={ setHandItemId } showCopyButton={ true } />
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number>(100);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
@@ -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={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCES } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } />
|
||||
</div>
|
||||
}>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number>(100);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||
{
|
||||
@@ -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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCES } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } />
|
||||
</div>
|
||||
}>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<NitroInput maxLength={ GetConfigurationValue<number>('wired.action.bot.talk.to.avatar.max.length', 64) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
|
||||
@@ -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<number>(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 (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } footer={ <WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCES } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } /> }>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<NitroInput maxLength={ GetConfigurationValue<number>('wired.action.bot.talk.max.length', 64) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
|
||||
@@ -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<number>(100);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
@@ -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={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ botSource } userSources={ BOT_SOURCES } usersTitle="wiredfurni.params.sources.users.title.bots" onChangeUsers={ value => setBotSource(normalizeBotSource(value, (botName.length > 0))) } />
|
||||
</div>
|
||||
}>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
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 (
|
||||
<WiredActionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
{ CONTROL_OPTIONS.map(option =>
|
||||
{
|
||||
return (
|
||||
<div key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (control === option.value) } className="form-check-input" id={ `controlClock${ option.value }` } name="controlClock" type="radio" onChange={ () => setControl(option.value) } />
|
||||
<Text>{ LocalizeText(option.label) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) }</Text>
|
||||
<Slider
|
||||
max={ 10 }
|
||||
min={ 1 }
|
||||
value={ time }
|
||||
onChange={ event => setTime(event) } />
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.choose_type') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (operation === value) } className="form-check-input" name="pointsOperation" type="radio" onChange={ () => setOperation(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.points_operation.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
@@ -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) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) }</Text>
|
||||
<Slider
|
||||
max={ 10 }
|
||||
min={ 1 }
|
||||
value={ time }
|
||||
onChange={ event => setTime(event) } />
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.choose_type') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (operation === value) } className="form-check-input" name="pointsOperation" type="radio" onChange={ () => setOperation(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.points_operation.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.choose_type') }</Text>
|
||||
{ [
|
||||
{ value: 0, label: 'Wired' },
|
||||
{ value: 1, label: 'Banzai' },
|
||||
{ value: 2, label: 'Freeze' }
|
||||
].map(option =>
|
||||
{
|
||||
return (
|
||||
<div key={ option.value } className="flex gap-1">
|
||||
<input checked={ (selectedTeamType === option.value) } className="form-check-input" id={ `selectedTeamType${ option.value }` } name="selectedTeamType" type="radio" onChange={ () => setSelectedTeamType(option.value) } />
|
||||
<Text>{ option.label }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
{ [ 1, 2, 3, 4 ].map(team =>
|
||||
|
||||
@@ -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 <WiredActionBotChangeFigureView />;
|
||||
case WiredActionLayoutCode.ADJUST_CLOCK:
|
||||
return <WiredActionAdjustClockView />;
|
||||
case WiredActionLayoutCode.BOT_FOLLOW_AVATAR:
|
||||
return <WiredActionBotFollowAvatarView />;
|
||||
case WiredActionLayoutCode.BOT_GIVE_HAND_ITEM:
|
||||
@@ -63,10 +83,14 @@ export const WiredActionLayoutView = (code: number) =>
|
||||
return <WiredActionFleeView />;
|
||||
case WiredActionLayoutCode.FREEZE:
|
||||
return <WiredActionFreezeView />;
|
||||
case WiredActionLayoutCode.CONTROL_CLOCK:
|
||||
return <WiredActionControlClockView />;
|
||||
case WiredActionLayoutCode.FURNI_TO_USER:
|
||||
return <WiredActionTeleportView />;
|
||||
case WiredActionLayoutCode.FURNI_TO_FURNI:
|
||||
return <WiredActionFurniToFurniView />;
|
||||
case WiredActionLayoutCode.SET_ALTITUDE:
|
||||
return <WiredActionSetAltitudeView />;
|
||||
case WiredActionLayoutCode.GIVE_REWARD:
|
||||
return <WiredActionGiveRewardView />;
|
||||
case WiredActionLayoutCode.GIVE_SCORE:
|
||||
@@ -83,6 +107,8 @@ export const WiredActionLayoutView = (code: number) =>
|
||||
return <WiredActionMoveFurniView />;
|
||||
case WiredActionLayoutCode.MOVE_AND_ROTATE_FURNI:
|
||||
return <WiredActionMoveAndRotateFurniView />;
|
||||
case WiredActionLayoutCode.MOVE_ROTATE_USER:
|
||||
return <WiredActionMoveRotateUserView />;
|
||||
case WiredActionLayoutCode.MOVE_FURNI_TO:
|
||||
return <WiredActionMoveFurniToView />;
|
||||
case WiredActionLayoutCode.MUTE_USER:
|
||||
@@ -107,10 +133,38 @@ export const WiredActionLayoutView = (code: number) =>
|
||||
return <WiredSelectorFurniNeighborhoodView />;
|
||||
case WiredActionLayoutCode.FURNI_BYTYPE_SELECTOR:
|
||||
return <WiredSelectorFurniByTypeView />;
|
||||
case WiredActionLayoutCode.FURNI_ALTITUDE_SELECTOR:
|
||||
return <WiredSelectorFurniAltitudeView />;
|
||||
case WiredActionLayoutCode.FURNI_ON_FURNI_SELECTOR:
|
||||
return <WiredSelectorFurniOnFurniView />;
|
||||
case WiredActionLayoutCode.FURNI_PICKS_SELECTOR:
|
||||
return <WiredSelectorFurniPicksView />;
|
||||
case WiredActionLayoutCode.FURNI_SIGNAL_SELECTOR:
|
||||
return <WiredSelectorFurniSignalView />;
|
||||
case WiredActionLayoutCode.USERS_AREA_SELECTOR:
|
||||
return <WiredSelectorUsersAreaView />;
|
||||
case WiredActionLayoutCode.USERS_NEIGHBORHOOD_SELECTOR:
|
||||
return <WiredSelectorUsersNeighborhoodView />;
|
||||
case WiredActionLayoutCode.USERS_SIGNAL_SELECTOR:
|
||||
return <WiredSelectorUsersSignalView />;
|
||||
case WiredActionLayoutCode.USERS_BY_TYPE_SELECTOR:
|
||||
return <WiredSelectorUsersByTypeView />;
|
||||
case WiredActionLayoutCode.USERS_BY_ACTION_SELECTOR:
|
||||
return <WiredSelectorUsersByActionView />;
|
||||
case WiredActionLayoutCode.USERS_BY_NAME_SELECTOR:
|
||||
return <WiredSelectorUsersByNameView />;
|
||||
case WiredActionLayoutCode.USERS_ON_FURNI_SELECTOR:
|
||||
return <WiredSelectorUsersOnFurniView />;
|
||||
case WiredActionLayoutCode.USERS_GROUP_SELECTOR:
|
||||
return <WiredSelectorUsersGroupView />;
|
||||
case WiredActionLayoutCode.USERS_HANDITEM_SELECTOR:
|
||||
return <WiredSelectorUsersHandItemView />;
|
||||
case WiredActionLayoutCode.USERS_TEAM_SELECTOR:
|
||||
return <WiredSelectorUsersTeamView />;
|
||||
case WiredActionLayoutCode.FILTER_FURNI_EXTRA:
|
||||
return <WiredExtraFilterFurniView />;
|
||||
case WiredActionLayoutCode.FILTER_USER_EXTRA:
|
||||
return <WiredExtraFilterUserView />;
|
||||
case WiredActionLayoutCode.SEND_SIGNAL:
|
||||
return <WiredActionSendSignalView />;
|
||||
}
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
@@ -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={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.startdir') }</Text>
|
||||
<div className="flex gap-1">
|
||||
{ directionOptions.map(option =>
|
||||
<div className="grid grid-cols-4 gap-2 max-w-[240px]">
|
||||
{ WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) =>
|
||||
{
|
||||
if(direction === null)
|
||||
{
|
||||
return <div key={ `move-to-dir-empty-${ rowIndex }-${ columnIndex }` } />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (movement === option.value) } className="form-check-input" id={ `movement${ option.value }` } name="movement" type="radio" onChange={ event => setMovement(option.value) } />
|
||||
<Text>
|
||||
<i className={ `icon icon-${ option.icon }` } />
|
||||
</Text>
|
||||
</div>
|
||||
<label key={ `move-to-dir-${ direction }` } className="flex items-center justify-center gap-[2px] cursor-pointer">
|
||||
<input checked={ (movement === direction) } className="form-check-input" id={ `movement${ direction }` } name="movement" type="radio" onChange={ () => setMovement(direction) } />
|
||||
<span className="inline-flex items-center justify-center">
|
||||
<WiredDirectionIcon direction={ direction } selected={ movement === direction } />
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
})) }
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -94,6 +84,13 @@ export const WiredActionMoveAndRotateFurniView: FC<{}> = props =>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.user_collide') }</Text>
|
||||
<label className="flex items-center gap-1 cursor-pointer">
|
||||
<input checked={ blockOnUserCollision } className="form-check-input" type="checkbox" onChange={ event => setBlockOnUserCollision(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.user_collide.0') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number, { value: number; icon: string }> = {
|
||||
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 =>
|
||||
<input checked={ (movement === 0) } className="form-check-input" id="movement0" name="selectedTeam" type="radio" onChange={ event => setMovement(0) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.movefurni.0') }</Text>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{ directionOptions.map(option =>
|
||||
<div className="grid grid-cols-4 gap-2 max-w-[240px]">
|
||||
{ WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) =>
|
||||
{
|
||||
if(direction === null)
|
||||
{
|
||||
return <div key={ `move-furni-empty-${ rowIndex }-${ columnIndex }` } />;
|
||||
}
|
||||
|
||||
const option = NORMAL_DIRECTION_VALUE_MAP[direction];
|
||||
|
||||
return (
|
||||
<label key={ `move-furni-${ direction }` } className="flex items-center justify-center gap-[2px] cursor-pointer">
|
||||
<input checked={ (movement === option.value) } className="form-check-input" id={ `movement${ option.value }` } name="movement" type="radio" onChange={ event => setMovement(option.value) } />
|
||||
<span className="inline-flex items-center justify-center">
|
||||
<WiredDirectionIcon direction={ option.value } iconSrc={ option.icon } selected={ movement === option.value } />
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})) }
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{ extraDirectionOptions.map(option =>
|
||||
{
|
||||
return (
|
||||
<div key={ option.value } className="flex items-center gap-1">
|
||||
<label key={ `extra-${ option.value }` } className="flex items-center gap-[2px] cursor-pointer">
|
||||
<input checked={ (movement === option.value) } className="form-check-input" id={ `movement${ option.value }` } name="movement" type="radio" onChange={ event => setMovement(option.value) } />
|
||||
<i className={ `nitro.icon icon-${ option.icon }` } />
|
||||
</div>
|
||||
<span className="inline-flex items-center justify-center">
|
||||
<WiredDirectionIcon direction={ option.value } iconSrc={ option.icon } selected={ movement === option.value } />
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
<div className="col" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
@@ -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<DirectionPickerProps> = props =>
|
||||
{
|
||||
const { name = '', title = '', noneLabel = '', value = -1, onChange = null, extraOptions = [] } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ title }</Text>
|
||||
<label className="flex items-center gap-2 text-[12px]">
|
||||
<input checked={ (value === -1) } className="form-check-input" name={ name } type="radio" onChange={ () => onChange(-1) } />
|
||||
<span>{ noneLabel }</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-4 gap-2 max-w-[240px]">
|
||||
{ WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) =>
|
||||
{
|
||||
if(direction === null)
|
||||
{
|
||||
return <div key={ `${ name }-empty-${ rowIndex }-${ columnIndex }` } />;
|
||||
}
|
||||
|
||||
const selected = (value === direction);
|
||||
|
||||
return (
|
||||
<label key={ `${ name }-${ direction }` } className="flex items-center justify-center gap-[2px] cursor-pointer">
|
||||
<input checked={ selected } className="form-check-input" name={ name } type="radio" onChange={ () => onChange(direction) } />
|
||||
<span className="inline-flex items-center justify-center">
|
||||
<WiredDirectionIcon direction={ direction } selected={ selected } />
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})) }
|
||||
</div>
|
||||
{ extraOptions.length > 0 &&
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{ extraOptions.map(option => (
|
||||
<label key={ `${ name }-extra-${ option.value }` } className="flex items-center gap-[2px] cursor-pointer" title={ option.label }>
|
||||
<input checked={ (value === option.value) } className="form-check-input" name={ name } type="radio" onChange={ () => onChange(option.value) } />
|
||||
<img alt="" className="h-auto w-auto object-contain" draggable={ false } src={ option.icon } />
|
||||
</label>
|
||||
)) }
|
||||
</div> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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<number>(() =>
|
||||
{
|
||||
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 (
|
||||
<WiredActionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<DirectionPicker
|
||||
name="wired-move-user-direction"
|
||||
title={ LocalizeText('wiredfurni.params.moveuser') }
|
||||
noneLabel={ LocalizeText('wiredfurni.params.movefurni.0') }
|
||||
value={ movementDirection }
|
||||
onChange={ setMovementDirection } />
|
||||
<DirectionPicker
|
||||
name="wired-rotate-user-direction"
|
||||
title={ LocalizeText('wiredfurni.params.rotateuser') }
|
||||
noneLabel={ LocalizeText('wiredfurni.params.rotatefurni.0') }
|
||||
extraOptions={ rotationExtraOptions }
|
||||
value={ rotationDirection }
|
||||
onChange={ setRotationDirection } />
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<number[]>([]);
|
||||
const [ forwardFurniIds, setForwardFurniIds ] = useState<number[]>([]);
|
||||
const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('antenna');
|
||||
const highlightedIds = useRef<number[]>([]);
|
||||
|
||||
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);
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
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 =>
|
||||
<input checked={ !!positionFlag } className="form-check-input" id="positionFlag" type="checkbox" onChange={ event => setPositionFlag(event.target.checked ? 1 : 0) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.condition.position') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ !!altitudeFlag } className="form-check-input" id="altitudeFlag" type="checkbox" onChange={ event => setAltitudeFlag(event.target.checked ? 1 : 0) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.condition.altitude') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
|
||||
@@ -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<boolean>(() =>
|
||||
{
|
||||
if(isTeleportEffect && trigger?.intData?.length >= 3) return (trigger.intData[0] === 1);
|
||||
return false;
|
||||
});
|
||||
const [ walkMode, setWalkMode ] = useState<number>(() =>
|
||||
{
|
||||
if(isUserToFurniEffect && trigger?.intData?.length >= 3) return trigger.intData[2];
|
||||
return 1;
|
||||
});
|
||||
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
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<number>(() =>
|
||||
{
|
||||
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 &&
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ fastTeleport } className="form-check-input" type="checkbox" onChange={ event => setFastTeleport(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.teleport.options.0') }</Text>
|
||||
</div> }
|
||||
{ isUserToFurniEffect &&
|
||||
<div className="flex flex-col gap-1">
|
||||
{ [ 0, 1, 2 ].map(option => (
|
||||
<label key={ option } className="flex items-center gap-1 cursor-pointer">
|
||||
<input checked={ walkMode === option } className="form-check-input" name="userWalkMode" type="radio" onChange={ () => setWalkMode(option) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.user_move.walkmode.${ option }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div> }
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 1) return trigger.intData[0];
|
||||
return 0;
|
||||
});
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
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={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> } />
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.operator.2') }</Text>
|
||||
{ [ 0, 1 ].map(option => (
|
||||
<label key={ option } className="flex items-center gap-1">
|
||||
<input checked={ (toggleType === option) } className="form-check-input" name="toggleType" type="radio" onChange={ () => setToggleType(option) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.toggletype.${ option }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="btn btn-link p-0 align-self-start" type="button" onClick={ () => setShowAdvanced(value => !value) }>
|
||||
{ LocalizeText(showAdvanced ? 'wiredfurni.params.sources.collapse' : 'wiredfurni.params.sources.expand') }
|
||||
</button>
|
||||
{ showAdvanced &&
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="actorDirQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } />
|
||||
</> }
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.direction_selection') }</Text>
|
||||
<div className="grid grid-cols-4 gap-2 max-w-[240px]">
|
||||
{ WIRED_DIRECTION_GRID.flatMap((row, rowIndex) => row.map((direction, columnIndex) =>
|
||||
{
|
||||
if(direction === null)
|
||||
{
|
||||
return <div key={ `actor-dir-empty-${ rowIndex }-${ columnIndex }` } />;
|
||||
}
|
||||
|
||||
const checked = ((directionMask & (1 << direction)) !== 0);
|
||||
|
||||
return (
|
||||
<label key={ `actor-dir-${ direction }` } className="flex items-center justify-center gap-[2px] cursor-pointer">
|
||||
<input checked={ checked } className="form-check-input" type="checkbox" onChange={ event => setDirectionMask(toggleDirection(directionMask, direction, event.target.checked)) } />
|
||||
<span className="inline-flex items-center justify-center">
|
||||
<WiredDirectionIcon direction={ direction } selected={ checked } />
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})) }
|
||||
</div>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<WiredConditionActorHasHandItemViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const [ handItemId, setHandItemId ] = useState(-1);
|
||||
const [ quantifier, setQuantifier ] = useState(0);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||
{
|
||||
@@ -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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.handitem') }</Text>
|
||||
<select className="form-select form-select-sm" value={ handItemId } onChange={ event => setHandItemId(parseInt(event.target.value)) }>
|
||||
{ ALLOWED_HAND_ITEM_IDS.map(value =>
|
||||
{
|
||||
return <option key={ value } value={ value }>{ LocalizeText(`handitem${ value }`) }</option>;
|
||||
}) }
|
||||
</select>
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="handItemQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<WiredHandItemField handItemId={ handItemId } onChange={ setHandItemId } showCopyButton={ true } />
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<WiredConditionActorIsGroupMemberViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const [ groups, setGroups ] = useState<HabboGroupEntryData[]>([]);
|
||||
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<number>(() =>
|
||||
|
||||
useMessageEvent<GuildMembershipsMessageEvent>(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 (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> } />
|
||||
footer={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="conditionGroupQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.groupselection') }</Text>
|
||||
{ [ GROUP_CURRENT_ROOM, GROUP_SELECTED ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (groupType === value) } className="form-check-input" name="conditionGroupType" type="radio" onChange={ () => setGroupType(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.grouptype.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
|
||||
{ (groupType === GROUP_SELECTED) &&
|
||||
<select className="form-select form-select-sm" value={ selectedGroupId } onChange={ event => setSelectedGroupId(parseInt(event.target.value)) }>
|
||||
{ !selectedGroupOptions.length && <option value={ 0 }>-</option> }
|
||||
{ selectedGroupOptions.map(group => <option key={ group.value } value={ group.value }>{ group.label }</option>) }
|
||||
</select> }
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<WiredConditionActorIsOnFurniViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
@@ -17,6 +23,11 @@ export const WiredConditionActorIsOnFurniView: FC<{}> = props =>
|
||||
if(trigger?.intData?.length > 1) return trigger.intData[1];
|
||||
return 0;
|
||||
});
|
||||
const [ quantifier, setQuantifier ] = useState<number>(() =>
|
||||
{
|
||||
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={ (
|
||||
<WiredSourcesSelector
|
||||
showFurni={ true }
|
||||
showUsers={ true }
|
||||
furniSource={ furniSource }
|
||||
userSource={ userSource }
|
||||
onChangeFurni={ onChangeFurniSource }
|
||||
onChangeUsers={ setUserSource } />
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="triggerOnFurniQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<WiredSourcesSelector
|
||||
showFurni={ true }
|
||||
showUsers={ true }
|
||||
furniSource={ furniSource }
|
||||
userSource={ userSource }
|
||||
onChangeFurni={ onChangeFurniSource }
|
||||
onChangeUsers={ setUserSource } />
|
||||
</div>
|
||||
) } />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<WiredConditionActorIsTeamMemberViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const [ selectedTeam, setSelectedTeam ] = useState(-1);
|
||||
const [ quantifier, setQuantifier ] = useState(1);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||
{
|
||||
@@ -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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="teamMemberQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
{ teamIds.map(value =>
|
||||
|
||||
@@ -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<WiredConditionActorIsWearingBadgeViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const [ badge, setBadge ] = useState('');
|
||||
const [ quantifier, setQuantifier ] = useState(1);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||
{
|
||||
@@ -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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="badgeQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.badgecode') }</Text>
|
||||
<NitroInput type="text" value={ badge } onChange={ event => setBadge(event.target.value) } />
|
||||
|
||||
@@ -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<WiredConditionActorIsWearingEffectViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const [ effect, setEffect ] = useState(-1);
|
||||
const [ quantifier, setQuantifier ] = useState(1);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||
{
|
||||
@@ -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={ <WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="effectQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.tooltip.effectid') }</Text>
|
||||
<NitroInput type="number" value={ effect } onChange={ event => setEffect(parseInt(event.target.value)) } />
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
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 (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="counterTimeQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.furni.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } />
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{ COMPARISON_OPTIONS.map(option =>
|
||||
{
|
||||
return (
|
||||
<div key={ option.value } className="flex items-center gap-1">
|
||||
<input checked={ (comparison === option.value) } className="form-check-input" id={ `counterComparison${ option.value }` } name="counterComparison" type="radio" onChange={ () => setComparison(option.value) } />
|
||||
<Text>{ LocalizeText(option.label) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.clock_minutes_elapsed', [ 'minutes' ], [ minutes.toString() ]) }</Text>
|
||||
<Slider
|
||||
max={ MINUTES_MAX }
|
||||
min={ MINUTES_MIN }
|
||||
step={ 1 }
|
||||
value={ minutes }
|
||||
onChange={ event => setMinutes(normalizeMinutes(event as number)) } />
|
||||
<Text small>{ minutes }</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.clock_seconds_elapsed', [ 'seconds' ], [ secondsLabel ]) }</Text>
|
||||
<Slider
|
||||
max={ HALF_SECONDS_MAX }
|
||||
min={ HALF_SECONDS_MIN }
|
||||
step={ 1 }
|
||||
value={ halfSeconds }
|
||||
onChange={ event => setHalfSeconds(normalizeHalfSeconds(event as number)) } />
|
||||
<Text small>{ secondsLabel }</Text>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<WiredConditionFurniHasAvatarOnViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ requireAll, setRequireAll ] = useState(0);
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
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={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> } />
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.requireall') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (requireAll === value) } className="form-check-input" name="furniHasAvatarRequireAll" type="radio" onChange={ () => setRequireAll(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.${ negative ? 'not_requireall' : 'requireall' }.${ value + 2 }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number>(() =>
|
||||
if(!data || !data.length) return [];
|
||||
|
||||
const ids = new Set<number>();
|
||||
|
||||
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<WiredConditionFurniIsOfTypeViewProps> = ({ negative = false }) =>
|
||||
{
|
||||
const [ matchSource, setMatchSource ] = useState<number>(SOURCE_TRIGGER);
|
||||
const [ compareSource, setCompareSource ] = useState<number>(SOURCE_TRIGGER);
|
||||
const [ quantifier, setQuantifier ] = useState<number>(0);
|
||||
const [ primaryFurniIds, setPrimaryFurniIds ] = useState<number[]>([]);
|
||||
const [ secondaryFurniIds, setSecondaryFurniIds ] = useState<number[]>([]);
|
||||
const [ selectionMode, setSelectionMode ] = useState<SelectionMode>('primary');
|
||||
|
||||
const highlightedIds = useRef<number[]>([]);
|
||||
|
||||
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 (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ requiresFurni }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> } />
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredSourcesSelector
|
||||
showFurni={ true }
|
||||
furniTitle="wiredfurni.params.sources.furni.title.match.0"
|
||||
furniSources={ MATCH_FURNI_SOURCES }
|
||||
furniSource={ matchSource }
|
||||
onChangeFurni={ setMatchSource } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<WiredSourcesSelector
|
||||
showFurni={ true }
|
||||
furniTitle="wiredfurni.params.sources.furni.title.match.1"
|
||||
furniSources={ MATCH_FURNI_SOURCES }
|
||||
furniSource={ compareSource }
|
||||
onChangeFurni={ setCompareSource } />
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="stuffIsQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`${ quantifierKeyPrefix }.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.sources.furni.title.match.0') }</Text>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={ (selectionMode === 'primary') ? 'primary' : 'secondary' }
|
||||
disabled={ matchSource !== SOURCE_SELECTED && compareSource !== SOURCE_SELECTED }
|
||||
onClick={ () => switchSelection('primary') }>
|
||||
{ LocalizeText('wiredfurni.params.sources.furni.100') }
|
||||
</Button>
|
||||
<Text small>{ selectionLimit ? `${ primaryFurniIds.length }/${ selectionLimit }` : primaryFurniIds.length }</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.sources.furni.title.match.1') }</Text>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={ (selectionMode === 'secondary') ? 'primary' : 'secondary' }
|
||||
disabled={ matchSource !== SOURCE_SECONDARY_SELECTED && compareSource !== SOURCE_SECONDARY_SELECTED }
|
||||
onClick={ () => switchSelection('secondary') }>
|
||||
{ LocalizeText('wiredfurni.params.sources.furni.101') }
|
||||
</Button>
|
||||
<Text small>{ selectionLimit ? `${ secondaryFurniIds.length }/${ selectionLimit }` : secondaryFurniIds.length }</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<WiredConditionFurniMatchesSnapshotViewProps> = ({ 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<number>(() =>
|
||||
{
|
||||
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={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } /> }>
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" name="snapshotQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.furni${ negative ? '.neg' : '' }.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ onChangeFurniSource } />
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.conditions') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -52,6 +79,10 @@ export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props =>
|
||||
<input checked={ !!positionFlag } className="form-check-input" id="positionFlag" type="checkbox" onChange={ event => setPositionFlag(event.target.checked ? 1 : 0) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.condition.position') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ !!altitudeFlag } className="form-check-input" id="altitudeFlag" type="checkbox" onChange={ event => setAltitudeFlag(event.target.checked ? 1 : 0) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.condition.altitude') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
|
||||
@@ -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 <WiredConditionActorHasHandItemView />;
|
||||
case WiredConditionlayout.NOT_ACTOR_HAS_HANDITEM:
|
||||
return <WiredConditionActorHasHandItemView negative={ true } />;
|
||||
case WiredConditionlayout.ACTOR_DIR:
|
||||
return <WiredConditionActorDirView />;
|
||||
case WiredConditionlayout.SLC_QUANTITY:
|
||||
return <WiredConditionSelectionQuantityView />;
|
||||
case WiredConditionlayout.TRIGGERER_MATCH:
|
||||
case WiredConditionlayout.NOT_TRIGGERER_MATCH:
|
||||
return <WiredConditionTriggererMatchView />;
|
||||
case WiredConditionlayout.NOT_TRIGGERER_MATCH:
|
||||
return <WiredConditionTriggererMatchView negative={ true } />;
|
||||
case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER:
|
||||
case WiredConditionlayout.NOT_ACTOR_IN_GROUP:
|
||||
return <WiredConditionActorIsGroupMemberView />;
|
||||
case WiredConditionlayout.NOT_ACTOR_IN_GROUP:
|
||||
return <WiredConditionActorIsGroupMemberView negative={ true } />;
|
||||
case WiredConditionlayout.ACTOR_IS_ON_FURNI:
|
||||
case WiredConditionlayout.NOT_ACTOR_ON_FURNI:
|
||||
return <WiredConditionActorIsOnFurniView />;
|
||||
case WiredConditionlayout.NOT_ACTOR_ON_FURNI:
|
||||
return <WiredConditionActorIsOnFurniView negative={ true } />;
|
||||
case WiredConditionlayout.ACTOR_IS_IN_TEAM:
|
||||
case WiredConditionlayout.NOT_ACTOR_IN_TEAM:
|
||||
return <WiredConditionActorIsTeamMemberView />;
|
||||
case WiredConditionlayout.NOT_ACTOR_IN_TEAM:
|
||||
return <WiredConditionActorIsTeamMemberView negative={ true } />;
|
||||
case WiredConditionlayout.ACTOR_IS_WEARING_BADGE:
|
||||
case WiredConditionlayout.NOT_ACTOR_WEARS_BADGE:
|
||||
return <WiredConditionActorIsWearingBadgeView />;
|
||||
case WiredConditionlayout.NOT_ACTOR_WEARS_BADGE:
|
||||
return <WiredConditionActorIsWearingBadgeView negative={ true } />;
|
||||
case WiredConditionlayout.ACTOR_IS_WEARING_EFFECT:
|
||||
case WiredConditionlayout.NOT_ACTOR_WEARING_EFFECT:
|
||||
return <WiredConditionActorIsWearingEffectView />;
|
||||
case WiredConditionlayout.NOT_ACTOR_WEARING_EFFECT:
|
||||
return <WiredConditionActorIsWearingEffectView negative={ true } />;
|
||||
case WiredConditionlayout.DATE_RANGE_ACTIVE:
|
||||
return <WiredConditionDateRangeView />;
|
||||
case WiredConditionlayout.MOVEMENT_VALIDATION:
|
||||
return <WiredConditionMovementValidationView />;
|
||||
case WiredConditionlayout.MATCH_TIME:
|
||||
return <WiredConditionMatchTimeView />;
|
||||
case WiredConditionlayout.MATCH_DATE:
|
||||
return <WiredConditionMatchDateView />;
|
||||
case WiredConditionlayout.FURNIS_HAVE_AVATARS:
|
||||
case WiredConditionlayout.FURNI_NOT_HAVE_HABBO:
|
||||
return <WiredConditionFurniHasAvatarOnView />;
|
||||
case WiredConditionlayout.FURNI_NOT_HAVE_HABBO:
|
||||
return <WiredConditionFurniHasAvatarOnView negative={ true } />;
|
||||
case WiredConditionlayout.HAS_STACKED_FURNIS:
|
||||
return <WiredConditionFurniHasFurniOnView />;
|
||||
case WiredConditionlayout.NOT_HAS_STACKED_FURNIS:
|
||||
return <WiredConditionFurniHasNotFurniOnView />;
|
||||
case WiredConditionlayout.STUFF_TYPE_MATCHES:
|
||||
case WiredConditionlayout.NOT_FURNI_IS_OF_TYPE:
|
||||
return <WiredConditionFurniIsOfTypeView />;
|
||||
case WiredConditionlayout.NOT_FURNI_IS_OF_TYPE:
|
||||
return <WiredConditionFurniIsOfTypeView negative={ true } />;
|
||||
case WiredConditionlayout.STATES_MATCH:
|
||||
case WiredConditionlayout.NOT_STATES_MATCH:
|
||||
return <WiredConditionFurniMatchesSnapshotView />;
|
||||
case WiredConditionlayout.NOT_STATES_MATCH:
|
||||
return <WiredConditionFurniMatchesSnapshotView negative={ true } />;
|
||||
case WiredConditionlayout.TIME_ELAPSED_LESS:
|
||||
return <WiredConditionTimeElapsedLessView />;
|
||||
case WiredConditionlayout.TIME_ELAPSED_MORE:
|
||||
|
||||
@@ -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 (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save } />
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.comparison_selection') }</Text>
|
||||
{ COMPARISON_OPTIONS.map(value =>
|
||||
{
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (comparison === value) } className="form-check-input" name="selectionQuantityComparison" type="radio" onChange={ () => setComparison(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.comparison.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
inputMode="numeric"
|
||||
max={ MAX_QUANTITY }
|
||||
min={ MIN_QUANTITY }
|
||||
type="text"
|
||||
value={ quantityInput }
|
||||
onBlur={ () => setQuantityInput(clampQuantity(quantity).toString()) }
|
||||
onChange={ event => updateQuantityInput(event.target.value) } />
|
||||
<Slider
|
||||
max={ MAX_QUANTITY }
|
||||
min={ MIN_QUANTITY }
|
||||
step={ 1 }
|
||||
value={ quantity }
|
||||
onChange={ event => updateQuantity(event as number) } />
|
||||
<Text small>{ quantity }</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.sources.merged.title') }</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
fullWidth
|
||||
variant={ (sourceGroup === SOURCE_GROUP_USERS) ? 'primary' : 'secondary' }
|
||||
onClick={ () => setSourceGroup(SOURCE_GROUP_USERS) }>
|
||||
{ LocalizeText('wiredfurni.params.sources.users.title') }
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant={ (sourceGroup === SOURCE_GROUP_FURNI) ? 'primary' : 'secondary' }
|
||||
onClick={ () => setSourceGroup(SOURCE_GROUP_FURNI) }>
|
||||
{ LocalizeText('wiredfurni.params.sources.furni.title') }
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="primary" className="px-2 py-1" onClick={ () => cycleSource(-1) }>
|
||||
<FaChevronLeft />
|
||||
</Button>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Text small>{ LocalizeText(activeSources[activeSourceIndex].label) }</Text>
|
||||
</div>
|
||||
<Button variant="primary" className="px-2 py-1" onClick={ () => cycleSource(1) }>
|
||||
<FaChevronRight />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<WiredConditionTriggererMatchViewProps> = ({ 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 (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" id={ `triggererMatchQuantifier${ value }` } name="triggererMatchQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }</Text>
|
||||
<Text>{ LocalizeText(`${ quantifierKeyPrefix }.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
|
||||
@@ -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<PropsWithChildren<WiredExtraBaseViewProps>> = 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 (
|
||||
<WiredBaseView hasSpecialInput={ hasSpecialInput } requiresFurni={ requiresFurni } save={ save } validate={ validate } wiredType="extra" cardStyle={ cardStyle } footer={ footer }>
|
||||
{ children }
|
||||
</WiredBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<HTMLInputElement>) =>
|
||||
{
|
||||
setAmount(clampValue(Number(event.target.value)));
|
||||
};
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([ clampValue(amount) ]);
|
||||
setStringParam('');
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } cardStyle={ { width: 360 } }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.setfilter') }</Text>
|
||||
<input className="form-control form-control-sm" max={ MAX_FILTER } min={ MIN_FILTER } type="number" value={ amount } onChange={ onChange } />
|
||||
</div>
|
||||
</WiredExtraBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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<HTMLInputElement>) =>
|
||||
{
|
||||
setAmount(clampValue(Number(event.target.value)));
|
||||
};
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([ clampValue(amount) ]);
|
||||
setStringParam('');
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredExtraBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } cardStyle={ { width: 360 } }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.setfilter') }</Text>
|
||||
<input className="form-control form-control-sm" max={ MAX_FILTER } min={ MIN_FILTER } type="number" value={ amount } onChange={ onChange } />
|
||||
</div>
|
||||
</WiredExtraBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: '385px'} }>
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: '385px'} }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.area_selection') }</Text>
|
||||
<Text small>{ LocalizeText('wiredfurni.params.area_selection.info') }</Text>
|
||||
@@ -154,6 +154,6 @@ export const WiredActionFurniAreaView: FC<{}> = props =>
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<PropsWithChildren<WiredSelectorBaseViewProps>> = props =>
|
||||
{
|
||||
const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, validate = null, hasSpecialInput = false, children = null, cardStyle = undefined, footer = null } = props;
|
||||
|
||||
return (
|
||||
<WiredBaseView hasSpecialInput={ hasSpecialInput } requiresFurni={ requiresFurni } save={ save } validate={ validate } wiredType="selector" cardStyle={ cardStyle } footer={ footer }>
|
||||
{ children }
|
||||
</WiredBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
{ [ 0, 1, 2 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (comparison === value) } className="form-check-input" name="furniAltitudeComparison" type="radio" onChange={ () => setComparison(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.comparison.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.setaltitude') }</Text>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
inputMode="decimal"
|
||||
type="text"
|
||||
value={ altitudeInput }
|
||||
onBlur={ () => setAltitudeInput(formatAltitude(altitude)) }
|
||||
onChange={ event => updateAltitudeInput(event.target.value) } />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Slider
|
||||
max={ MAX_ALTITUDE }
|
||||
min={ MIN_ALTITUDE }
|
||||
step={ ALTITUDE_STEP }
|
||||
value={ altitude }
|
||||
onChange={ event => updateAltitude(event as number) } />
|
||||
<Text small>{ formatAltitude(altitude) }</Text>
|
||||
</div>
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
@@ -76,6 +76,6 @@ export const WiredSelectorFurniByTypeView: FC<{}> = () =>
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<GridProps> = ({ selectedTiles, invert, onToggle }) =>
|
||||
const NeighborhoodGrid: FC<GridProps> = ({ 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<HTMLDivElement>, 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<HTMLDivElement>, 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<GridProps> = ({ 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(
|
||||
<div key={ `b-${ rx }-${ ry }` } style={ border } />,
|
||||
isTarget && (
|
||||
<svg key={ `o-${ rx }-${ ry }` } style={ targetOutline } viewBox={ `0 0 ${ TILE_W + 4 } ${ TILE_H + 4 }` }>
|
||||
<polygon
|
||||
points={ `${ (TILE_W + 4) / 2 },2 ${ TILE_W + 2 },${ (TILE_H + 4) / 2 } ${ (TILE_W + 4) / 2 },${ TILE_H + 2 } 2,${ (TILE_H + 4) / 2 }` }
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
strokeWidth="1" />
|
||||
</svg>
|
||||
),
|
||||
<div
|
||||
key={ `t-${ rx }-${ ry }` }
|
||||
style={ diamond }
|
||||
title={ `(${ rx }, ${ ry })` }
|
||||
onClick={ () => !isCenter && onToggle(rx, ry) }
|
||||
/>,
|
||||
onMouseDown={ event => beginTileDrag(event, rx, ry, isTarget, isSelected) }
|
||||
onMouseEnter={ event => continueTileDrag(event, rx, ry, isTarget) } />,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={ { position: 'relative', width: GRID_PX_W, height: GRID_PX_H, flexShrink: 0 } }>
|
||||
<div style={ { position: 'relative', width: GRID_PX_W, height: GRID_PX_H, flexShrink: 0 } } onContextMenu={ event => event.preventDefault() }>
|
||||
{ tiles }
|
||||
</div>
|
||||
);
|
||||
@@ -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<Tile>({ 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 (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: '400px' } }>
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: '400px' } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.neighborhood_selection') }</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant={ targetPlacementMode ? 'success' : 'secondary' }
|
||||
className="px-2 py-1"
|
||||
onClick={ () => setTargetPlacementMode(value => !value) }
|
||||
title="Sposta target">
|
||||
<span aria-hidden className="relative inline-block h-[14px] w-[14px]">
|
||||
<span className="absolute left-1/2 top-0 h-full w-px -translate-x-1/2 bg-current" />
|
||||
<span className="absolute left-0 top-1/2 h-px w-full -translate-y-1/2 bg-current" />
|
||||
<span className="absolute left-1/2 top-1/2 h-[8px] w-[8px] -translate-x-1/2 -translate-y-1/2 rounded-full border border-current" />
|
||||
</span>
|
||||
</Button>
|
||||
<Button variant="success" className="px-2 py-1" onClick={ addTile } title={ LocalizeText('wiredfurni.tooltip.select.tile') }>
|
||||
<FaPlus />
|
||||
</Button>
|
||||
@@ -299,7 +362,13 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () =>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<NeighborhoodGrid selectedTiles={ selectedTiles } invert={ invert } onToggle={ toggleTile } />
|
||||
<NeighborhoodGrid
|
||||
selectedTiles={ selectedTiles }
|
||||
targetTile={ targetTile }
|
||||
invert={ invert }
|
||||
onSetTile={ setTileSelection }
|
||||
onMoveTarget={ moveTargetTile }
|
||||
targetPlacementMode={ targetPlacementMode } />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -378,6 +447,6 @@ export const WiredSelectorFurniNeighborhoodView: FC<{}> = () =>
|
||||
</Text> }
|
||||
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<number>(() =>
|
||||
{
|
||||
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 (
|
||||
<WiredSelectorBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
hideDelay={ true }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selection_type') }</Text>
|
||||
{ [ 0, 1, 2, 3 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (selectionType === value) } className="form-check-input" name="furniOnFurniSelectionType" type="radio" onChange={ () => setSelectionType(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.onfurni.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: '385px' } }>
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: '385px' } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.area_selection') }</Text>
|
||||
<Text small>{ LocalizeText('wiredfurni.params.area_selection.info') }</Text>
|
||||
@@ -134,25 +130,7 @@ export const WiredSelectorUsersAreaView: FC<{}> = props =>
|
||||
onChange={ e => setInvert(e.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ excludeBots }
|
||||
onChange={ e => setExcludeBots(e.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.bot') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ excludePets }
|
||||
onChange={ e => setExcludePets(e.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.pet') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>Action</Text>
|
||||
<select className="form-select form-select-sm" value={ selectedAction } onChange={ event => setSelectedAction(parseInt(event.target.value)) }>
|
||||
{ ACTION_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{ (selectedAction === ACTION_SIGN) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="flex items-center gap-1">
|
||||
<input checked={ signFilterEnabled } className="form-check-input" type="checkbox" onChange={ event => setSignFilterEnabled(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.sign_filter') }</Text>
|
||||
</label>
|
||||
{ signFilterEnabled &&
|
||||
<select className="form-select form-select-sm" value={ signId } onChange={ event => setSignId(parseInt(event.target.value)) }>
|
||||
{ SIGN_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
|
||||
</select> }
|
||||
</div> }
|
||||
|
||||
{ (selectedAction === ACTION_DANCE) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="flex items-center gap-1">
|
||||
<input checked={ danceFilterEnabled } className="form-check-input" type="checkbox" onChange={ event => setDanceFilterEnabled(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.dance_filter') }</Text>
|
||||
</label>
|
||||
{ danceFilterEnabled &&
|
||||
<select className="form-select form-select-sm" value={ danceId } onChange={ event => setDanceId(parseInt(event.target.value)) }>
|
||||
{ DANCE_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
|
||||
</select> }
|
||||
</div> }
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.enter_names') }</Text>
|
||||
<textarea
|
||||
className="form-control form-control-sm min-h-[140px] resize-none"
|
||||
value={ namesText }
|
||||
onChange={ event => setNamesText(event.target.value) } />
|
||||
</div>
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSelectorBaseView } from './WiredSelectorBaseView';
|
||||
|
||||
const USER_TYPES = [ 1, 2, 4 ];
|
||||
|
||||
export const WiredSelectorUsersByTypeView: FC<{}> = () =>
|
||||
{
|
||||
const [ userType, setUserType ] = 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;
|
||||
|
||||
setUserType(params.length > 0 ? params[0] : 1);
|
||||
setFilterExisting(params.length > 1 ? (params[1] === 1) : false);
|
||||
setInvert(params.length > 2 ? (params[2] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = useCallback(() =>
|
||||
{
|
||||
setIntParams([
|
||||
userType,
|
||||
filterExisting ? 1 : 0,
|
||||
invert ? 1 : 0
|
||||
]);
|
||||
}, [ userType, filterExisting, invert, setIntParams ]);
|
||||
|
||||
return (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.usertype') }</Text>
|
||||
{ USER_TYPES.map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (userType === value) } className="form-check-input" name="usersByTypeSelector" type="radio" onChange={ () => setUserType(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.usertype.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
import { CatalogGroupsComposer, GuildMembershipsMessageEvent, HabboGroupEntryData } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText, SendMessageComposer } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useMessageEvent, useWired } from '../../../../hooks';
|
||||
import { WiredSelectorBaseView } from './WiredSelectorBaseView';
|
||||
|
||||
const GROUP_CURRENT_ROOM = 0;
|
||||
const GROUP_SELECTED = 1;
|
||||
|
||||
export const WiredSelectorUsersGroupView: FC<{}> = () =>
|
||||
{
|
||||
const [ groups, setGroups ] = useState<HabboGroupEntryData[]>([]);
|
||||
const [ groupType, setGroupType ] = useState(GROUP_CURRENT_ROOM);
|
||||
const [ selectedGroupId, setSelectedGroupId ] = useState(0);
|
||||
const [ filterExisting, setFilterExisting ] = useState(false);
|
||||
const [ invert, setInvert ] = useState(false);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
useMessageEvent<GuildMembershipsMessageEvent>(GuildMembershipsMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setGroups(parser.groups || []);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new CatalogGroupsComposer());
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const params = trigger.intData;
|
||||
|
||||
setGroupType(params.length > 0 ? params[0] : GROUP_CURRENT_ROOM);
|
||||
setSelectedGroupId(params.length > 1 ? params[1] : 0);
|
||||
setFilterExisting(params.length > 2 ? (params[2] === 1) : false);
|
||||
setInvert(params.length > 3 ? (params[3] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
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([
|
||||
groupType,
|
||||
selectedGroupId,
|
||||
filterExisting ? 1 : 0,
|
||||
invert ? 1 : 0
|
||||
]);
|
||||
|
||||
return (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.groupselection') }</Text>
|
||||
{ [ GROUP_CURRENT_ROOM, GROUP_SELECTED ].map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (groupType === value) } className="form-check-input" name="usersGroupSelectorType" type="radio" onChange={ () => setGroupType(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.grouptype.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
|
||||
{ (groupType === GROUP_SELECTED) &&
|
||||
<select className="form-select form-select-sm" value={ selectedGroupId } onChange={ event => setSelectedGroupId(parseInt(event.target.value)) }>
|
||||
{ !selectedGroupOptions.length && <option value={ 0 }>-</option> }
|
||||
{ selectedGroupOptions.map(group => <option key={ group.value } value={ group.value }>{ group.label }</option>) }
|
||||
</select> }
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSelectorBaseView } from './WiredSelectorBaseView';
|
||||
import { WiredHandItemField } from '../WiredHandItemField';
|
||||
|
||||
export const WiredSelectorUsersHandItemView: FC<{}> = () =>
|
||||
{
|
||||
const [ handItemId, setHandItemId ] = useState(0);
|
||||
const [ filterExisting, setFilterExisting ] = useState(false);
|
||||
const [ invert, setInvert ] = useState(false);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const params = trigger.intData;
|
||||
|
||||
setHandItemId(params.length > 0 ? params[0] : 0);
|
||||
setFilterExisting(params.length > 1 ? (params[1] === 1) : false);
|
||||
setInvert(params.length > 2 ? (params[2] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = useCallback(() =>
|
||||
{
|
||||
setIntParams([
|
||||
handItemId,
|
||||
filterExisting ? 1 : 0,
|
||||
invert ? 1 : 0
|
||||
]);
|
||||
}, [ handItemId, filterExisting, invert, setIntParams ]);
|
||||
|
||||
return (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<WiredHandItemField handItemId={ handItemId } onChange={ setHandItemId } showCopyButton={ true } />
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,18 @@
|
||||
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 { LocalizeText } 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;
|
||||
const SOURCE_USER_CLICKED = 2;
|
||||
const SOURCE_FURNI_TRIGGER = 3;
|
||||
const SOURCE_FURNI_PICKED = 4;
|
||||
const SOURCE_FURNI_SIGNAL = 5;
|
||||
|
||||
const USER_SOURCES = [
|
||||
{ value: SOURCE_USER_TRIGGER, label: 'wiredfurni.params.sources.users.0' },
|
||||
@@ -16,6 +20,12 @@ const USER_SOURCES = [
|
||||
{ value: SOURCE_USER_CLICKED, label: 'wiredfurni.params.sources.users.11' },
|
||||
];
|
||||
|
||||
const FURNI_SOURCES = [
|
||||
{ value: SOURCE_FURNI_TRIGGER, label: 'wiredfurni.params.sources.furni.0' },
|
||||
{ value: SOURCE_FURNI_PICKED, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: SOURCE_FURNI_SIGNAL, label: 'wiredfurni.params.sources.furni.201' },
|
||||
];
|
||||
|
||||
const TILE_W = 22;
|
||||
const TILE_H = 11;
|
||||
const GRID_RANGE = 4;
|
||||
@@ -37,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<GridProps> = ({ selectedTiles, invert, onToggle }) =>
|
||||
const NeighborhoodGrid: FC<GridProps> = ({ 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<HTMLDivElement>, 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<HTMLDivElement>, 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';
|
||||
@@ -76,36 +127,61 @@ const NeighborhoodGrid: FC<GridProps> = ({ 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(
|
||||
<div key={ `b-${ rx }-${ ry }` } style={ border } />,
|
||||
isTarget && (
|
||||
<svg key={ `o-${ rx }-${ ry }` } style={ targetOutline } viewBox={ `0 0 ${ TILE_W + 4 } ${ TILE_H + 4 }` }>
|
||||
<polygon
|
||||
points={ `${ (TILE_W + 4) / 2 },2 ${ TILE_W + 2 },${ (TILE_H + 4) / 2 } ${ (TILE_W + 4) / 2 },${ TILE_H + 2 } 2,${ (TILE_H + 4) / 2 }` }
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
strokeWidth="1" />
|
||||
</svg>
|
||||
),
|
||||
<div
|
||||
key={ `t-${ rx }-${ ry }` }
|
||||
style={ diamond }
|
||||
title={ `(${ rx }, ${ ry })` }
|
||||
onClick={ () => !isCenter && onToggle(rx, ry) }
|
||||
/>,
|
||||
onMouseDown={ event => beginTileDrag(event, rx, ry, isTarget, isSelected) }
|
||||
onMouseEnter={ event => continueTileDrag(event, rx, ry, isTarget) } />,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={ { position: 'relative', width: GRID_PX_W, height: GRID_PX_H, flexShrink: 0 } }>
|
||||
<div style={ { position: 'relative', width: GRID_PX_W, height: GRID_PX_H, flexShrink: 0 } } onContextMenu={ event => event.preventDefault() }>
|
||||
{ tiles }
|
||||
</div>
|
||||
);
|
||||
@@ -117,19 +193,29 @@ export const WiredSelectorUsersNeighborhoodView: FC<{}> = () =>
|
||||
const [ filterExisting, setFilterExisting ] = useState(false);
|
||||
const [ invert, setInvert ] = useState(false);
|
||||
const [ sourceType, setSourceType ] = useState(SOURCE_USER_TRIGGER);
|
||||
const [ targetTile, setTargetTile ] = useState<Tile>({ x: 0, y: 0 });
|
||||
const [ targetPlacementMode, setTargetPlacementMode ] = useState(false);
|
||||
const [ curX, setCurX ] = useState(0);
|
||||
const [ curY, setCurY ] = useState(0);
|
||||
|
||||
const { trigger = null, setIntParams } = useWired();
|
||||
const { trigger = null, furniIds = [], setIntParams } = useWired();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
GetRoomEngine().areaSelectionManager.clearHighlight();
|
||||
GetRoomEngine().areaSelectionManager.deactivate();
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const p = trigger.intData;
|
||||
if(p.length >= 1) setSourceType(Math.min(p[0], SOURCE_USER_CLICKED));
|
||||
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 >= 6)
|
||||
{
|
||||
@@ -156,25 +242,41 @@ export const WiredSelectorUsersNeighborhoodView: FC<{}> = () =>
|
||||
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 ]);
|
||||
@@ -202,21 +304,50 @@ export const WiredSelectorUsersNeighborhoodView: FC<{}> = () =>
|
||||
setSelectedTiles(tiles);
|
||||
}, []);
|
||||
|
||||
const sourceIndex = USER_SOURCES.findIndex(s => s.value === sourceType);
|
||||
const isUserGroup = sourceType <= SOURCE_USER_CLICKED;
|
||||
const activeSources = isUserGroup ? USER_SOURCES : FURNI_SOURCES;
|
||||
const groupOffset = isUserGroup ? 0 : SOURCE_FURNI_TRIGGER;
|
||||
const groupIndex = sourceType - groupOffset;
|
||||
|
||||
const prevSource = () =>
|
||||
setSourceType(USER_SOURCES[(sourceIndex - 1 + USER_SOURCES.length) % USER_SOURCES.length].value);
|
||||
setSourceType(groupOffset + ((groupIndex - 1 + activeSources.length) % activeSources.length));
|
||||
|
||||
const nextSource = () =>
|
||||
setSourceType(USER_SOURCES[(sourceIndex + 1) % USER_SOURCES.length].value);
|
||||
setSourceType(groupOffset + ((groupIndex + 1) % activeSources.length));
|
||||
|
||||
const switchGroup = (toUser: boolean) =>
|
||||
{
|
||||
if(toUser === isUserGroup) return;
|
||||
|
||||
const newOffset = toUser ? 0 : SOURCE_FURNI_TRIGGER;
|
||||
setSourceType(newOffset + groupIndex);
|
||||
};
|
||||
|
||||
const requiresFurni = sourceType === SOURCE_FURNI_PICKED
|
||||
? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID
|
||||
: WiredFurniType.STUFF_SELECTION_OPTION_NONE;
|
||||
|
||||
const pickedCount = furniIds.length;
|
||||
const pickedLimit = trigger?.maximumItemSelectionCount ?? 20;
|
||||
|
||||
return (
|
||||
<WiredActionBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: '400px' } }>
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ requiresFurni } save={ save } hideDelay={ true } cardStyle={ { width: '400px' } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.neighborhood_selection') }</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant={ targetPlacementMode ? 'success' : 'secondary' }
|
||||
className="px-2 py-1"
|
||||
onClick={ () => setTargetPlacementMode(value => !value) }
|
||||
title="Sposta target">
|
||||
<span aria-hidden className="relative inline-block h-[14px] w-[14px]">
|
||||
<span className="absolute left-1/2 top-0 h-full w-px -translate-x-1/2 bg-current" />
|
||||
<span className="absolute left-0 top-1/2 h-px w-full -translate-y-1/2 bg-current" />
|
||||
<span className="absolute left-1/2 top-1/2 h-[8px] w-[8px] -translate-x-1/2 -translate-y-1/2 rounded-full border border-current" />
|
||||
</span>
|
||||
</Button>
|
||||
<Button variant="success" className="px-2 py-1" onClick={ addTile } title={ LocalizeText('wiredfurni.tooltip.select.tile') }>
|
||||
<FaPlus />
|
||||
</Button>
|
||||
@@ -232,7 +363,13 @@ export const WiredSelectorUsersNeighborhoodView: FC<{}> = () =>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<NeighborhoodGrid selectedTiles={ selectedTiles } invert={ invert } onToggle={ toggleTile } />
|
||||
<NeighborhoodGrid
|
||||
selectedTiles={ selectedTiles }
|
||||
targetTile={ targetTile }
|
||||
invert={ invert }
|
||||
onSetTile={ setTileSelection }
|
||||
onMoveTarget={ moveTargetTile }
|
||||
targetPlacementMode={ targetPlacementMode } />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -282,15 +419,35 @@ export const WiredSelectorUsersNeighborhoodView: FC<{}> = () =>
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.sources.merged.title.neighborhood') }</Text>
|
||||
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
fullWidth
|
||||
variant={ isUserGroup ? 'primary' : 'secondary' }
|
||||
onClick={ () => switchGroup(true) }>
|
||||
{ LocalizeText('wiredfurni.params.furni_neighborhood.group.user') }
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant={ !isUserGroup ? 'primary' : 'secondary' }
|
||||
onClick={ () => switchGroup(false) }>
|
||||
{ LocalizeText('wiredfurni.params.furni_neighborhood.group.furni') }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="primary" className="px-2 py-1" onClick={ prevSource }>‹</Button>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Text small>{ LocalizeText(USER_SOURCES[sourceIndex].label) }</Text>
|
||||
<Text small>{ LocalizeText(activeSources[groupIndex].label) }</Text>
|
||||
</div>
|
||||
<Button variant="primary" className="px-2 py-1" onClick={ nextSource }>›</Button>
|
||||
</div>
|
||||
|
||||
{ sourceType === SOURCE_FURNI_PICKED &&
|
||||
<Text small className="text-center">
|
||||
{ LocalizeText('wiredfurni.pickfurnis.caption', [ 'count', 'limit' ], [ pickedCount.toString(), pickedLimit.toString() ]) }
|
||||
</Text> }
|
||||
|
||||
</div>
|
||||
</WiredActionBaseView>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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 WiredSelectorUsersOnFurniView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ filterExisting, setFilterExisting ] = useState(false);
|
||||
const [ invert, setInvert ] = useState(false);
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 0) return trigger.intData[0];
|
||||
return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setFurniSource((trigger.intData.length > 0) ? trigger.intData[0] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0));
|
||||
setFilterExisting((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false);
|
||||
setInvert((trigger.intData.length > 2) ? (trigger.intData[2] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = () => setIntParams([
|
||||
furniSource,
|
||||
filterExisting ? 1 : 0,
|
||||
invert ? 1 : 0
|
||||
]);
|
||||
|
||||
return (
|
||||
<WiredSelectorBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
hideDelay={ true }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -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 WiredSelectorUsersSignalView: 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 (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSelectorBaseView } from './WiredSelectorBaseView';
|
||||
|
||||
const TEAM_TYPES = [ 0, 1, 2, 3, 4 ];
|
||||
|
||||
export const WiredSelectorUsersTeamView: FC<{}> = () =>
|
||||
{
|
||||
const [ teamType, setTeamType ] = useState(0);
|
||||
const [ filterExisting, setFilterExisting ] = useState(false);
|
||||
const [ invert, setInvert ] = useState(false);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const params = trigger.intData;
|
||||
|
||||
setTeamType(params.length > 0 ? params[0] : 0);
|
||||
setFilterExisting(params.length > 1 ? (params[1] === 1) : false);
|
||||
setInvert(params.length > 2 ? (params[2] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = useCallback(() =>
|
||||
{
|
||||
setIntParams([
|
||||
teamType,
|
||||
filterExisting ? 1 : 0,
|
||||
invert ? 1 : 0
|
||||
]);
|
||||
}, [ teamType, filterExisting, invert, setIntParams ]);
|
||||
|
||||
return (
|
||||
<WiredSelectorBaseView hasSpecialInput={ true } requiresFurni={ 0 } save={ save } hideDelay={ true } cardStyle={ { width: 400 } }>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
{ TEAM_TYPES.map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (teamType === value) } className="form-check-input" name="usersTeamSelector" type="radio" onChange={ () => setTeamType(value) } />
|
||||
<Text>{ LocalizeText((value === 0) ? 'wiredfurni.params.team.any' : `wiredfurni.params.team.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
|
||||
<hr className="m-0 bg-dark" />
|
||||
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.selector_options_selector') }</Text>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ filterExisting }
|
||||
onChange={ event => setFilterExisting(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.0') }</Text>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={ invert }
|
||||
onChange={ event => setInvert(event.target.checked) } />
|
||||
<Text small>{ LocalizeText('wiredfurni.params.selector_option.1') }</Text>
|
||||
</label>
|
||||
</div>
|
||||
</WiredSelectorBaseView>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
@@ -6,22 +5,34 @@ import { useWired } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
export const WiredTriggerAvatarSaysSomethingView: FC<{}> = props =>
|
||||
const MATCH_CONTAINS = 0;
|
||||
const MATCH_EXACT = 1;
|
||||
const MATCH_ALL = 2;
|
||||
|
||||
export const WiredTriggerAvatarSaysSomethingView: FC<{}> = () =>
|
||||
{
|
||||
const [ message, setMessage ] = useState('');
|
||||
const [ triggererAvatar, setTriggererAvatar ] = useState(-1);
|
||||
const [ matchMode, setMatchMode ] = useState(MATCH_CONTAINS);
|
||||
const [ hideMessage, setHideMessage ] = useState(false);
|
||||
const [ ownerOnly, setOwnerOnly ] = useState(false);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setStringParam(message);
|
||||
setIntParams([ triggererAvatar ]);
|
||||
setIntParams([
|
||||
matchMode,
|
||||
hideMessage ? 1 : 0,
|
||||
ownerOnly ? 1 : 0
|
||||
]);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setMessage(trigger.stringData);
|
||||
setTriggererAvatar((trigger.intData.length > 0) ? trigger.intData[0] : 0);
|
||||
setMessage(trigger?.stringData ?? '');
|
||||
setMatchMode((trigger?.intData?.length > 0) ? trigger.intData[0] : MATCH_CONTAINS);
|
||||
setHideMessage((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
|
||||
setOwnerOnly((trigger?.intData?.length > 2) ? (trigger.intData[2] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
@@ -31,14 +42,27 @@ export const WiredTriggerAvatarSaysSomethingView: FC<{}> = props =>
|
||||
<NitroInput type="text" value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.picktriggerer') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (triggererAvatar === 0) } className="form-check-input" id="triggererAvatar0" name="triggererAvatar" type="radio" onChange={ event => setTriggererAvatar(0) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.anyavatar') }</Text>
|
||||
<input checked={ (matchMode === MATCH_CONTAINS) } className="form-check-input" id="sayMatchContains" name="sayMatchMode" type="radio" onChange={ () => setMatchMode(MATCH_CONTAINS) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.chatcontains') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (triggererAvatar === 1) } className="form-check-input" id="triggererAvatar1" name="triggererAvatar" type="radio" onChange={ event => setTriggererAvatar(1) } />
|
||||
<Text>{ GetSessionDataManager().userName }</Text>
|
||||
<input checked={ (matchMode === MATCH_EXACT) } className="form-check-input" id="sayMatchExact" name="sayMatchMode" type="radio" onChange={ () => setMatchMode(MATCH_EXACT) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.exactmatch') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (matchMode === MATCH_ALL) } className="form-check-input" id="sayMatchAll" name="sayMatchMode" type="radio" onChange={ () => setMatchMode(MATCH_ALL) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.allmatch') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ hideMessage } className="form-check-input" id="sayHideMessage" type="checkbox" onChange={ event => setHideMessage(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.chat.hide') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ ownerOnly } className="form-check-input" id="sayOwnerOnly" type="checkbox" onChange={ event => setOwnerOnly(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.chat.onlyowner') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
</WiredTriggerBaseView>
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { WiredFurniType } from '../../../../api';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
export const WiredTriggerAvatarWalksOffFurniView: FC<{}> = props =>
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerAvatarWalksOffFurniView: FC<{}> = () =>
|
||||
{
|
||||
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
|
||||
const save = () => setIntParams([ furniSource ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setFurniSource((trigger?.intData?.length > 0) ? normalizeFurniSource(trigger.intData[0]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ false }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ FURNI_SOURCE_OPTIONS } onChangeFurni={ setFurniSource } /> } />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { WiredFurniType } from '../../../../api';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
export const WiredTriggerAvatarWalksOnFurniView: FC<{}> = props =>
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerAvatarWalksOnFurniView: FC<{}> = () =>
|
||||
{
|
||||
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
|
||||
const save = () => setIntParams([ furniSource ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setFurniSource((trigger?.intData?.length > 0) ? normalizeFurniSource(trigger.intData[0]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ false }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ FURNI_SOURCE_OPTIONS } onChangeFurni={ setFurniSource } /> } />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, PropsWithChildren } from 'react';
|
||||
import { FC, PropsWithChildren, ReactNode } from 'react';
|
||||
import { WiredFurniType } from '../../../../api';
|
||||
import { WiredBaseView } from '../WiredBaseView';
|
||||
|
||||
@@ -7,16 +7,17 @@ export interface WiredTriggerBaseViewProps
|
||||
hasSpecialInput: boolean;
|
||||
requiresFurni: number;
|
||||
save: () => void;
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
export const WiredTriggerBaseView: FC<PropsWithChildren<WiredTriggerBaseViewProps>> = props =>
|
||||
{
|
||||
const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props;
|
||||
const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null, footer = null } = props;
|
||||
|
||||
const onSave = () => (save && save());
|
||||
|
||||
return (
|
||||
<WiredBaseView hasSpecialInput={ hasSpecialInput } requiresFurni={ requiresFurni } save={ onSave } wiredType="trigger">
|
||||
<WiredBaseView hasSpecialInput={ hasSpecialInput } requiresFurni={ requiresFurni } save={ onSave } wiredType="trigger" footer={ footer }>
|
||||
{ children }
|
||||
</WiredBaseView>
|
||||
);
|
||||
|
||||
@@ -3,26 +3,52 @@ import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const BOT_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.users.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.users.200' }
|
||||
];
|
||||
|
||||
const normalizeBotSource = (value: number) => (BOT_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerBotReachedAvatarView: FC<{}> = props =>
|
||||
{
|
||||
const [ botName, setBotName ] = useState('');
|
||||
const { trigger = null, setStringParam = null } = useWired();
|
||||
const [ botSource, setBotSource ] = useState(100);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
|
||||
const save = () => setStringParam(botName);
|
||||
const save = () =>
|
||||
{
|
||||
setStringParam((botSource === 100) ? botName : '');
|
||||
setIntParams([ botSource ]);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setBotName(trigger.stringData);
|
||||
setBotSource((trigger?.intData?.length > 0) ? normalizeBotSource(trigger.intData[0]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={
|
||||
<WiredSourcesSelector
|
||||
showUsers={ true }
|
||||
userSource={ botSource }
|
||||
userSources={ BOT_SOURCE_OPTIONS }
|
||||
usersTitle="wiredfurni.params.sources.users.title.bots"
|
||||
onChangeUsers={ setBotSource } />
|
||||
}>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
</WiredTriggerBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,26 +3,64 @@ import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const BOT_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.users.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.users.200' }
|
||||
];
|
||||
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
const normalizeBotSource = (value: number) => (BOT_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerBotReachedStuffView: FC<{}> = props =>
|
||||
{
|
||||
const [ botName, setBotName ] = useState('');
|
||||
const { trigger = null, setStringParam = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
const [ botSource, setBotSource ] = useState(100);
|
||||
const { trigger = null, setStringParam = null, setIntParams = null } = useWired();
|
||||
|
||||
const save = () => setStringParam(botName);
|
||||
const save = () =>
|
||||
{
|
||||
setStringParam((botSource === 100) ? botName : '');
|
||||
setIntParams([ furniSource, botSource ]);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setBotName(trigger.stringData);
|
||||
setFurniSource((trigger?.intData?.length > 0) ? normalizeFurniSource(trigger.intData[0]) : 100);
|
||||
setBotSource((trigger?.intData?.length > 1) ? normalizeBotSource(trigger.intData[1]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ save }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div>
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={
|
||||
<WiredSourcesSelector
|
||||
showFurni={ true }
|
||||
showUsers={ true }
|
||||
furniSource={ furniSource }
|
||||
userSource={ botSource }
|
||||
furniSources={ FURNI_SOURCE_OPTIONS }
|
||||
userSources={ BOT_SOURCE_OPTIONS }
|
||||
usersTitle="wiredfurni.params.sources.users.title.bots"
|
||||
onChangeFurni={ setFurniSource }
|
||||
onChangeUsers={ setBotSource } />
|
||||
}>
|
||||
{ (botSource === 100) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.bot.name') }</Text>
|
||||
<NitroInput maxLength={ 32 } type="text" value={ botName } onChange={ event => setBotName(event.target.value) } />
|
||||
</div> }
|
||||
</WiredTriggerBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { WiredFurniType } from '../../../../api';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerClickFurniView: FC<{}> = () =>
|
||||
{
|
||||
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
|
||||
const save = () => setIntParams([ furniSource ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setFurniSource((trigger?.intData?.length > 0) ? normalizeFurniSource(trigger.intData[0]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ false }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ FURNI_SOURCE_OPTIONS } onChangeFurni={ setFurniSource } /> } />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,46 @@
|
||||
import { FC, useEffect } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { WiredFurniType } from '../../../../api';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const CLICK_TILE_INTERACTION_TYPES = [ 'room_invisible_click_tile' ];
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerClickTileView: FC<{}> = () =>
|
||||
{
|
||||
const { setAllowedInteractionTypes } = useWired();
|
||||
const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
|
||||
const save = () => setIntParams([ furniSource ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setFurniSource((trigger?.intData?.length > 0) ? normalizeFurniSource(trigger.intData[0]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setAllowedInteractionTypes(CLICK_TILE_INTERACTION_TYPES);
|
||||
setAllowedInteractionErrorKey('wiredfurni.error.require_click_tiles');
|
||||
|
||||
return () => setAllowedInteractionTypes(null);
|
||||
}, [ setAllowedInteractionTypes ]);
|
||||
return () =>
|
||||
{
|
||||
setAllowedInteractionTypes(null);
|
||||
setAllowedInteractionErrorKey(null);
|
||||
};
|
||||
}, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]);
|
||||
|
||||
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
|
||||
return (
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ false }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ FURNI_SOURCE_OPTIONS } onChangeFurni={ setFurniSource } /> } />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
import { FC } from 'react';
|
||||
import { WiredFurniType } from '../../../../api';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
export const WiredTriggerClickUserView: FC<{}> = () =>
|
||||
{
|
||||
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ null } />;
|
||||
const [ blockMenuOpen, setBlockMenuOpen ] = useState(false);
|
||||
const [ doNotRotate, setDoNotRotate ] = useState(false);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
const save = () => setIntParams([
|
||||
blockMenuOpen ? 1 : 0,
|
||||
doNotRotate ? 1 : 0
|
||||
]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setBlockMenuOpen((trigger?.intData?.length > 0) ? (trigger.intData[0] === 1) : false);
|
||||
setDoNotRotate((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ blockMenuOpen } className="form-check-input" id="clickUserBlockMenuOpen" type="checkbox" onChange={ event => setBlockMenuOpen(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.click_user.block_menu_open') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ doNotRotate } className="form-check-input" id="clickUserDoNotRotate" type="checkbox" onChange={ event => setDoNotRotate(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.click_user.do_not_rotate') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
</WiredTriggerBaseView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Slider, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourcesSelector, WiredSourceOption } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
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 TRIGGER_FURNI_SOURCES: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
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 normalizeFurniSource = (value: number) => (TRIGGER_FURNI_SOURCES.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
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 WiredTriggerClockCounterView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired();
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 2) return normalizeFurniSource(trigger.intData[2]);
|
||||
return 100;
|
||||
});
|
||||
const [ minutes, setMinutes ] = useState(0);
|
||||
const [ halfSeconds, setHalfSeconds ] = useState(0);
|
||||
|
||||
const secondsLabel = useMemo(() => formatSeconds(halfSeconds), [ halfSeconds ]);
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
normalizeMinutes(minutes),
|
||||
normalizeHalfSeconds(halfSeconds),
|
||||
normalizeFurniSource(furniSource)
|
||||
]);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setMinutes((trigger.intData.length > 0) ? normalizeMinutes(trigger.intData[0]) : 0);
|
||||
setHalfSeconds((trigger.intData.length > 1) ? normalizeHalfSeconds(trigger.intData[1]) : 0);
|
||||
setFurniSource((trigger.intData.length > 2) ? normalizeFurniSource(trigger.intData[2]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES);
|
||||
setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni');
|
||||
|
||||
return () =>
|
||||
{
|
||||
setAllowedInteractionTypes(null);
|
||||
setAllowedInteractionErrorKey(null);
|
||||
};
|
||||
}, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ TRIGGER_FURNI_SOURCES } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.clock_minutes_elapsed', [ 'minutes' ], [ minutes.toString() ]) }</Text>
|
||||
<Slider
|
||||
max={ MINUTES_MAX }
|
||||
min={ MINUTES_MIN }
|
||||
step={ 1 }
|
||||
value={ minutes }
|
||||
onChange={ event => setMinutes(normalizeMinutes(event as number)) } />
|
||||
<Text small>{ minutes }</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.clock_seconds_elapsed', [ 'seconds' ], [ secondsLabel ]) }</Text>
|
||||
<Slider
|
||||
max={ HALF_SECONDS_MAX }
|
||||
min={ HALF_SECONDS_MIN }
|
||||
step={ 1 }
|
||||
value={ halfSeconds }
|
||||
onChange={ event => setHalfSeconds(normalizeHalfSeconds(event as number)) } />
|
||||
<Text small>{ secondsLabel }</Text>
|
||||
</div>
|
||||
</WiredTriggerBaseView>
|
||||
);
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import { WiredTriggerBotReachedStuffView } from './WiredTriggerBotReachedStuffVi
|
||||
import { WiredTriggerClickFurniView } from './WiredTriggerClickFurniView';
|
||||
import { WiredTriggerClickTileView } from './WiredTriggerClickTileView';
|
||||
import { WiredTriggerClickUserView } from './WiredTriggerClickUserView';
|
||||
import { WiredTriggerClockCounterView } from './WiredTriggerClockCounterView';
|
||||
import { WiredTriggerCollisionView } from './WiredTriggerCollisionView';
|
||||
import { WiredTriggerUserPerformsActionView } from './WiredTriggerUserPerformsActionView';
|
||||
import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView';
|
||||
@@ -45,6 +46,8 @@ export const WiredTriggerLayoutView = (code: number) =>
|
||||
return <WiredTriggerClickTileView />;
|
||||
case WiredTriggerLayout.CLICK_USER:
|
||||
return <WiredTriggerClickUserView />;
|
||||
case WiredTriggerLayout.CLOCK_COUNTER:
|
||||
return <WiredTriggerClockCounterView />;
|
||||
case WiredTriggerLayout.USER_PERFORMS_ACTION:
|
||||
return <WiredTriggerUserPerformsActionView />;
|
||||
case WiredTriggerLayout.COLLISION:
|
||||
|
||||
@@ -2,35 +2,59 @@ import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const ANTENNA_INTERACTION_TYPES = [ 'antenna' ];
|
||||
const ANTENNA_ERROR_MESSAGE = 'Puoi selezionare solo furni antenna.';
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerReceiveSignalView: FC<{}> = () =>
|
||||
{
|
||||
const [ senderCount, setSenderCount ] = useState(0);
|
||||
const [ maxSenders, setMaxSenders ] = useState(5);
|
||||
const [ channel, setChannel ] = useState(0);
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
|
||||
const { trigger = null, setAllowedInteractionTypes } = useWired();
|
||||
const { trigger = null, setIntParams = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired();
|
||||
|
||||
const save = () => setIntParams([ channel, furniSource ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const p = trigger.intData;
|
||||
if(p.length >= 1) setChannel(p[0]);
|
||||
if(p.length >= 2) setSenderCount(p[1]);
|
||||
if(p.length >= 3) setMaxSenders(p[2]);
|
||||
if(p.length >= 4) setFurniSource(normalizeFurniSource(p[3]));
|
||||
else setFurniSource(100);
|
||||
}, [ trigger ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setAllowedInteractionTypes(ANTENNA_INTERACTION_TYPES);
|
||||
setAllowedInteractionErrorKey(ANTENNA_ERROR_MESSAGE);
|
||||
|
||||
return () => setAllowedInteractionTypes(null);
|
||||
}, [ setAllowedInteractionTypes ]);
|
||||
return () =>
|
||||
{
|
||||
setAllowedInteractionTypes(null);
|
||||
setAllowedInteractionErrorKey(null);
|
||||
};
|
||||
}, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID } save={ null }>
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ FURNI_SOURCE_OPTIONS } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex items-center justify-between">
|
||||
<Text small>{ LocalizeText('wiredfurni.params.signal.senders_connected') }</Text>
|
||||
<Text bold small>{ senderCount }/{ maxSenders }</Text>
|
||||
|
||||
@@ -4,16 +4,20 @@ import { Slider, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const TEAM_TYPES = [ 0, 1, 2, 3, 4 ];
|
||||
|
||||
export const WiredTriggeScoreAchievedView: FC<{}> = props =>
|
||||
{
|
||||
const [ points, setPoints ] = useState(1);
|
||||
const [ teamType, setTeamType ] = useState(0);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
const save = () => setIntParams([ points ]);
|
||||
const save = () => setIntParams([ points, teamType ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setPoints((trigger.intData.length > 0) ? trigger.intData[0] : 0);
|
||||
setTeamType((trigger.intData.length > 1) ? trigger.intData[1] : 0);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
@@ -25,6 +29,14 @@ export const WiredTriggeScoreAchievedView: FC<{}> = props =>
|
||||
min={ 1 }
|
||||
value={ points }
|
||||
onChange={ event => setPoints(event) } />
|
||||
<hr className="m-0 bg-dark" />
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
{ TEAM_TYPES.map(value => (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (teamType === value) } className="form-check-input" name="scoreAchievedTeamType" type="radio" onChange={ () => setTeamType(value) } />
|
||||
<Text>{ LocalizeText((value === 0) ? 'wiredfurni.params.team.any' : `wiredfurni.params.team.${ value }`) }</Text>
|
||||
</label>
|
||||
)) }
|
||||
</div>
|
||||
</WiredTriggerBaseView>
|
||||
);
|
||||
|
||||
@@ -2,22 +2,36 @@ import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
|
||||
|
||||
const FURNI_SOURCE_OPTIONS: WiredSourceOption[] = [
|
||||
{ value: 100, label: 'wiredfurni.params.sources.furni.100' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.furni.200' }
|
||||
];
|
||||
|
||||
const normalizeFurniSource = (value: number) => (FURNI_SOURCE_OPTIONS.some(option => (option.value === value)) ? value : 100);
|
||||
|
||||
export const WiredTriggerToggleFurniView: FC<{}> = () =>
|
||||
{
|
||||
const [ triggerMode, setTriggerMode ] = useState(0);
|
||||
const [ furniSource, setFurniSource ] = useState(100);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
|
||||
const save = () => setIntParams([ triggerMode ]);
|
||||
const save = () => setIntParams([ triggerMode, furniSource ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setTriggerMode((trigger?.intData?.length > 0) ? trigger.intData[0] : 0);
|
||||
setFurniSource((trigger?.intData?.length > 1) ? normalizeFurniSource(trigger.intData[1]) : 100);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID } save={ save }>
|
||||
<WiredTriggerBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } furniSources={ FURNI_SOURCE_OPTIONS } onChangeFurni={ setFurniSource } /> }>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.condition.state') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
input {
|
||||
width: auto;
|
||||
min-width: 1em;
|
||||
grid-area: 1 / 2;
|
||||
grid-area: 1 / 1;
|
||||
margin: 0;
|
||||
resize: none;
|
||||
background: none;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GetRoomEngine, GetSessionDataManager, RoomEngineObjectEvent, RoomEngineUseProductEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionPetInfoUpdateEvent, RoomSessionPetStatusUpdateEvent, RoomSessionUserDataUpdateEvent } from '@nitrots/nitro-renderer';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { AvatarInfoFurni, AvatarInfoName, AvatarInfoPet, AvatarInfoRentableBot, AvatarInfoUser, AvatarInfoUtilities, CanManipulateFurniture, FurniCategory, IAvatarInfo, IsOwnerOfFurniture, RoomWidgetUpdateRoomObjectEvent, UseProductItem } from '../../../api';
|
||||
import { useNitroEvent, useUiEvent } from '../../events';
|
||||
import { useFriends } from '../../friends';
|
||||
@@ -9,6 +9,7 @@ import { useRoom } from '../useRoom';
|
||||
|
||||
const useAvatarInfoWidgetState = () =>
|
||||
{
|
||||
const CLICK_USER_DEBOUNCE_MS = 120;
|
||||
const [ avatarInfo, setAvatarInfo ] = useState<IAvatarInfo>(null);
|
||||
const [ activeNameBubble, setActiveNameBubble ] = useState<AvatarInfoName>(null);
|
||||
const [ nameBubbles, setNameBubbles ] = useState<AvatarInfoName[]>([]);
|
||||
@@ -16,10 +17,26 @@ const useAvatarInfoWidgetState = () =>
|
||||
const [ confirmingProduct, setConfirmingProduct ] = useState<UseProductItem>(null);
|
||||
const [ pendingPetId, setPendingPetId ] = useState<number>(-1);
|
||||
const [ isDecorating, setIsDecorating ] = useState(false);
|
||||
const pendingAvatarInfoTimeout = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
const { friends = [] } = useFriends();
|
||||
const { selectObjectForWired = null } = useWired();
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const clearPendingAvatarInfo = () =>
|
||||
{
|
||||
if(!pendingAvatarInfoTimeout.current) return;
|
||||
|
||||
clearTimeout(pendingAvatarInfoTimeout.current);
|
||||
pendingAvatarInfoTimeout.current = null;
|
||||
};
|
||||
|
||||
const isAvatarMenuBlocked = () =>
|
||||
{
|
||||
const control = (globalThis as any).__nitroAvatarClickControl;
|
||||
|
||||
return !!control && (control.suppressMenuUntil > Date.now());
|
||||
};
|
||||
|
||||
const removeNameBubble = (index: number) =>
|
||||
{
|
||||
setNameBubbles(prevValue =>
|
||||
@@ -64,6 +81,8 @@ const useAvatarInfoWidgetState = () =>
|
||||
|
||||
const getObjectInfo = (objectId: number, category: number) =>
|
||||
{
|
||||
clearPendingAvatarInfo();
|
||||
|
||||
let info: IAvatarInfo = null;
|
||||
|
||||
switch(category)
|
||||
@@ -101,7 +120,20 @@ const useAvatarInfoWidgetState = () =>
|
||||
|
||||
if(!info) return;
|
||||
|
||||
setAvatarInfo(info);
|
||||
if(category !== RoomObjectCategory.UNIT)
|
||||
{
|
||||
setAvatarInfo(info);
|
||||
return;
|
||||
}
|
||||
|
||||
pendingAvatarInfoTimeout.current = setTimeout(() =>
|
||||
{
|
||||
pendingAvatarInfoTimeout.current = null;
|
||||
|
||||
if(isAvatarMenuBlocked()) return;
|
||||
|
||||
setAvatarInfo(info);
|
||||
}, CLICK_USER_DEBOUNCE_MS);
|
||||
};
|
||||
|
||||
const processUsableRoomObject = (objectId: number) =>
|
||||
@@ -271,6 +303,7 @@ const useAvatarInfoWidgetState = () =>
|
||||
|
||||
useObjectDeselectedEvent(event =>
|
||||
{
|
||||
clearPendingAvatarInfo();
|
||||
setAvatarInfo(null);
|
||||
setProductBubbles([]);
|
||||
});
|
||||
@@ -344,6 +377,11 @@ const useAvatarInfoWidgetState = () =>
|
||||
setNameBubbles([]);
|
||||
}, [ activeNameBubble ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () => clearPendingAvatarInfo();
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!roomSession) return;
|
||||
|
||||
@@ -17,6 +17,7 @@ const useWiredState = () =>
|
||||
const [ neighborhoodTiles, setNeighborhoodTiles ] = useState<{ x: number; y: number }[] | null>(null);
|
||||
const [ neighborhoodInvert, setNeighborhoodInvert ] = useState<boolean>(false);
|
||||
const [ allowedInteractionTypes, setAllowedInteractionTypes ] = useState<string[] | null>(null);
|
||||
const [ allowedInteractionErrorKey, setAllowedInteractionErrorKey ] = useState<string | null>(null);
|
||||
const { showConfirm = null, simpleAlert = null } = useNotification();
|
||||
|
||||
const saveWired = () =>
|
||||
@@ -84,6 +85,17 @@ const useWiredState = () =>
|
||||
return allowedInteractionTypes.some(type => (type && type.toLowerCase() === interactionType));
|
||||
};
|
||||
|
||||
const handleDisallowedInteraction = () =>
|
||||
{
|
||||
if(!allowedInteractionErrorKey) return;
|
||||
|
||||
const message = (/^[a-z0-9_.]+$/i.test(allowedInteractionErrorKey))
|
||||
? LocalizeText(allowedInteractionErrorKey)
|
||||
: allowedInteractionErrorKey;
|
||||
|
||||
simpleAlert(message, null, null, null, LocalizeText('wiredfurni.title'));
|
||||
};
|
||||
|
||||
if(selectByType && category === RoomObjectCategory.FLOOR)
|
||||
{
|
||||
const roomId = GetRoomSession().roomId;
|
||||
@@ -97,6 +109,7 @@ const useWiredState = () =>
|
||||
if(!sourceFurniData) return;
|
||||
if(!isAllowedInteraction(sourceFurniData))
|
||||
{
|
||||
handleDisallowedInteraction();
|
||||
setFurniIds(prevValue =>
|
||||
{
|
||||
if(!prevValue.includes(objectId)) return prevValue;
|
||||
@@ -185,6 +198,7 @@ const useWiredState = () =>
|
||||
if(!sourceFurniData) return;
|
||||
if(!isAllowedInteraction(sourceFurniData))
|
||||
{
|
||||
handleDisallowedInteraction();
|
||||
setFurniIds(prevValue =>
|
||||
{
|
||||
if(!prevValue.includes(objectId)) return prevValue;
|
||||
@@ -245,7 +259,9 @@ const useWiredState = () =>
|
||||
|
||||
if(parser.info && parser.info.length)
|
||||
{
|
||||
simpleAlert(parser.info, null, null, null, LocalizeText('wiredfurni.title'));
|
||||
const message = (/^[a-z0-9_.]+$/i.test(parser.info) ? LocalizeText(parser.info) : parser.info);
|
||||
|
||||
simpleAlert(message, null, null, null, LocalizeText('wiredfurni.title'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -291,10 +307,11 @@ const useWiredState = () =>
|
||||
setNeighborhoodTiles(null);
|
||||
setNeighborhoodInvert(false);
|
||||
setAllowedInteractionTypes(null);
|
||||
setAllowedInteractionErrorKey(null);
|
||||
};
|
||||
}, [ trigger ]);
|
||||
|
||||
return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired, setSelectByType, setNeighborhoodTiles, setNeighborhoodInvert, setAllowedInteractionTypes };
|
||||
return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired, setSelectByType, setNeighborhoodTiles, setNeighborhoodInvert, setAllowedInteractionTypes, setAllowedInteractionErrorKey };
|
||||
};
|
||||
|
||||
export const useWired = () => useBetween(useWiredState);
|
||||
|
||||