{ roomSession instanceof RoomSession &&
<>
diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx
index dde184d..6a504d2 100644
--- a/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx
+++ b/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx
@@ -45,8 +45,7 @@ const BadgeMiniPicker: FC<{
return (
e.stopPropagation() }>
= props
canUse = true;
isCrackable = true;
- crackableHits = stuffData?.hits ?? 0;
- crackableTarget = stuffData?.target ?? 0;
+ crackableHits = stuffData.hits;
+ crackableTarget = stuffData.target;
}
else if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX)
@@ -458,7 +458,7 @@ export const InfoStandWidgetFurniView: FC
= props
return (
-
+
@@ -527,7 +527,7 @@ export const InfoStandWidgetFurniView: FC = props
{ isCrackable &&
<>
- { LocalizeText('infostand.crackable_furni.hits_remaining', [ 'hits', 'target' ], [ (crackableHits ?? 0).toString(), (crackableTarget ?? 0).toString() ]) }
+ { LocalizeText('infostand.crackable_furni.hits_remaining', [ 'hits', 'target' ], [ crackableHits.toString(), crackableTarget.toString() ]) }
> }
{ avatarInfo.groupId > 0 &&
<>
@@ -552,21 +552,7 @@ export const InfoStandWidgetFurniView: FC = props
{ godMode &&
<>
- { canSeeFurniId &&
-
-
-
-
ID: { avatarInfo.id }
-
-
-
-
Sprite: { (() => { const ro = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR); return ro?.model?.getValue(RoomObjectVariable.FURNITURE_TYPE_ID) ?? '?'; })() }
-
-
}
+ { canSeeFurniId && ID: { avatarInfo.id } }
{ (!avatarInfo.isWallItem && canMove) &&
<>
-
{ dropdownOpen &&
{ /* Left panel: position + rotation */ }
diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx
index 2dac49e..1791979 100644
--- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx
+++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx
@@ -1,4 +1,4 @@
-import { CreateLinkEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
+import { GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useCallback, useEffect, useState } from 'react';
import { FaPencilAlt, FaTimes } from 'react-icons/fa';
import { AvatarInfoUser, CloneObject, GetConfigurationValue, GetGroupInformation, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api';
@@ -7,6 +7,7 @@ import { useMessageEvent, useNitroEvent, useRoom } from '../../../../../hooks';
import { InfoStandBadgeSlotView } from './InfoStandBadgeSlotView';
import { InfoStandWidgetUserRelationshipsView } from './InfoStandWidgetUserRelationshipsView';
import { InfoStandWidgetUserTagsView } from './InfoStandWidgetUserTagsView';
+import { BackgroundsView } from '../../../../backgrounds/BackgroundsView';
interface InfoStandWidgetUserViewProps {
avatarInfo: AvatarInfoUser;
@@ -31,7 +32,7 @@ export const InfoStandWidgetUserView: FC
= props =
const handleProfileClick = useCallback(() => { GetUserProfile(avatarInfo.webID); }, [avatarInfo.webID]);
- const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); CreateLinkEvent('interface-settings/profile'); }, []);
+ const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); setIsVisible(prev => !prev); }, []);
const saveMotto = (motto: string) => {
if (!isEditingMotto || motto.length > GetConfigurationValue('motto.max.length', 38) || !roomSession) return;
@@ -126,7 +127,7 @@ export const InfoStandWidgetUserView: FC = props =
return (
<>
-
+
@@ -256,6 +257,19 @@ export const InfoStandWidgetUserView: FC
= props =
)}
+ {isVisible && avatarInfo.type === AvatarInfoUser.OWN_USER && (
+
+
+
+ )}
>
);
};
\ No newline at end of file
diff --git a/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx b/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx
index ff26177..a0513cf 100644
--- a/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx
+++ b/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx
@@ -3,21 +3,16 @@ import { Flex, FlexProps } from '../../../../common';
export const ContextMenuHeaderView: FC = props =>
{
- const { justifyContent = 'center', alignItems = 'center', classNames = [], style = {}, ...rest } = props;
+ const { justifyContent = 'center', alignItems = 'center', classNames = [], ...rest } = props;
const getClassNames = useMemo(() =>
{
- const newClassNames: string[] = [ 'text-[#fff] min-w-[117px] h-[25px] max-h-[25px] text-[16px] mb-[2px]', 'p-1' ];
+ const newClassNames: string[] = [ 'bg-[#3d5f6e] text-[#fff] min-w-[117px] h-[25px] max-h-[25px] text-[16px] mb-[2px]', 'p-1' ];
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ classNames ]);
- const mergedStyle = useMemo(() => ({
- backgroundColor: 'var(--ui-ctx-header-bg, #3d5f6e)',
- ...style
- }), [ style ]);
-
- return ;
+ return ;
};
diff --git a/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx b/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx
index b012a93..0a1eacc 100644
--- a/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx
+++ b/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx
@@ -8,7 +8,7 @@ interface ContextMenuListItemViewProps extends FlexProps
export const ContextMenuListItemView: FC = props =>
{
- const { disabled = false, fullWidth = true, justifyContent = 'center', alignItems = 'center', classNames = [], style = {}, onClick = null, ...rest } = props;
+ const { disabled = false, fullWidth = true, justifyContent = 'center', alignItems = 'center', classNames = [], onClick = null, ...rest } = props;
const handleClick = (event: MouseEvent) =>
{
@@ -19,7 +19,7 @@ export const ContextMenuListItemView: FC = props =
const getClassNames = useMemo(() =>
{
- const newClassNames: string[] = [ 'relative mb-[2px] p-[3px] overflow-hidden', 'h-[24px] max-h-[24px] p-[3px] cursor-pointer' ];
+ const newClassNames: string[] = [ 'relative mb-[2px] p-[3px] overflow-hidden', 'h-[24px] max-h-[24px] p-[3px] bg-[repeating-linear-gradient(#131e25,#131e25_50%,#0d171d_50%,#0d171d_100%)] cursor-pointer' ];
if(disabled) newClassNames.push('disabled');
@@ -28,10 +28,5 @@ export const ContextMenuListItemView: FC = props =
return newClassNames;
}, [ disabled, classNames ]);
- const mergedStyle = useMemo(() => ({
- background: 'repeating-linear-gradient(var(--ui-ctx-item-bg1, #131e25), var(--ui-ctx-item-bg1, #131e25) 50%, var(--ui-ctx-item-bg2, #0d171d) 50%, var(--ui-ctx-item-bg2, #0d171d) 100%)',
- ...style
- }), [ style ]);
-
- return ;
+ return ;
};
diff --git a/src/components/room/widgets/context-menu/ContextMenuView.tsx b/src/components/room/widgets/context-menu/ContextMenuView.tsx
index b92dc89..1ca3e83 100644
--- a/src/components/room/widgets/context-menu/ContextMenuView.tsx
+++ b/src/components/room/widgets/context-menu/ContextMenuView.tsx
@@ -76,6 +76,7 @@ export const ContextMenuView: FC = ({
const getClassNames = useMemo(() => {
const classes = [
'p-[2px]!',
+ 'bg-[#1c323f]',
'border-2',
'border-[solid]',
'border-[rgba(255,255,255,.5)]',
@@ -97,7 +98,6 @@ export const ContextMenuView: FC = ({
top: pos.y ?? 0,
transition: isFading ? 'opacity 75ms linear' : undefined,
opacity,
- backgroundColor: 'var(--ui-ctx-bg, #1c323f)',
...style,
}),
[pos, opacity, isFading, style]
diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx
index 62503bb..7d6743a 100644
--- a/src/components/toolbar/ToolbarView.tsx
+++ b/src/components/toolbar/ToolbarView.tsx
@@ -69,38 +69,38 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
)}
-
-
-
- {
- setMeExpanded(!isMeExpanded);
- event.stopPropagation();
- } }>
-
- { (getTotalUnseen > 0) &&
- }
+
+
+
+
+ {
+ setMeExpanded(!isMeExpanded);
+ event.stopPropagation();
+ } }>
+
+ { (getTotalUnseen > 0) &&
+ }
+
+ { isInRoom &&
+ VisitDesktop() } /> }
+ { !isInRoom &&
+ CreateLinkEvent('navigator/goto/home') } /> }
+ CreateLinkEvent('navigator/toggle') } />
+ { GetConfigurationValue('game.center.enabled') &&
+ CreateLinkEvent('games/toggle') } /> }
+ CreateLinkEvent('catalog/toggle') } />
+ CreateLinkEvent('inventory/toggle') }>
+ { (getFullCount > 0) &&
+ }
+
+ { isInRoom &&
+ CreateLinkEvent('camera/toggle') } /> }
+ { isMod &&
+ CreateLinkEvent('mod-tools/toggle') } /> }
- { isInRoom &&
- VisitDesktop() } /> }
- { !isInRoom &&
- CreateLinkEvent('navigator/goto/home') } /> }
- CreateLinkEvent('navigator/toggle') } />
- { GetConfigurationValue('game.center.enabled') &&
- CreateLinkEvent('games/toggle') } /> }
- CreateLinkEvent('catalog/toggle') } />
- CreateLinkEvent('inventory/toggle') }>
- { (getFullCount > 0) &&
- }
-
- { isInRoom &&
- CreateLinkEvent('camera/toggle') } /> }
- { isMod &&
- CreateLinkEvent('mod-tools/toggle') } /> }
- { isMod &&
- CreateLinkEvent('furni-editor/toggle') } /> }
+
-
-
+
CreateLinkEvent('friends/toggle') }>
{ (requests.length > 0) &&
diff --git a/src/components/wired-tools/WiredCreatorToolsView.tsx b/src/components/wired-tools/WiredCreatorToolsView.tsx
new file mode 100644
index 0000000..7d5d4ed
--- /dev/null
+++ b/src/components/wired-tools/WiredCreatorToolsView.tsx
@@ -0,0 +1,176 @@
+import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
+import { FC, useEffect, useMemo, useState } from 'react';
+import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common';
+
+type WiredToolsTab = 'monitor' | 'variables' | 'inspection' | 'chests' | 'settings';
+
+interface MonitorStat
+{
+ label: string;
+ value: string;
+}
+
+interface MonitorLog
+{
+ type: string;
+ category: string;
+ amount: string;
+ latest: string;
+}
+
+const TABS: Array<{ key: WiredToolsTab; label: string; }> = [
+ { key: 'monitor', label: 'Monitor' },
+ { key: 'variables', label: 'Variables' },
+ { key: 'inspection', label: 'Inspection' },
+ { key: 'chests', label: 'Chests' },
+ { key: 'settings', label: 'Settings' }
+];
+
+const MONITOR_STATS: MonitorStat[] = [
+ { label: 'Wired usage', value: '0/10000' },
+ { label: 'Is heavy', value: 'No' },
+ { label: 'Floor furni', value: '0/4000' },
+ { label: 'Wall furni', value: '0/4000' },
+ { label: 'Permanent furni vars', value: '0/60' }
+];
+
+const MONITOR_LOGS: MonitorLog[] = [
+ { type: 'EXECUTION_CAP', category: 'ERROR', amount: '0', latest: '/' },
+ { type: 'DELAYED_EVENTS_CAP', category: 'ERROR', amount: '0', latest: '/' },
+ { type: 'EXECUTOR_OVERLOAD', category: 'ERROR', amount: '0', latest: '/' },
+ { type: 'MARKED_AS_HEAVY', category: 'WARNING', amount: '0', latest: '/' },
+ { type: 'KILLED', category: 'ERROR', amount: '0', latest: '/' },
+ { type: 'RECURSION_TIMEOUT', category: 'ERROR', amount: '0', latest: '/' }
+];
+
+export const WiredCreatorToolsView: FC<{}> = props =>
+{
+ const [ isVisible, setIsVisible ] = useState(false);
+ const [ activeTab, setActiveTab ] = useState('monitor');
+
+ useEffect(() =>
+ {
+ const linkTracker: ILinkEventTracker = {
+ linkReceived: (url: string) =>
+ {
+ const parts = url.split('/');
+
+ if(parts.length < 2) return;
+
+ switch(parts[1])
+ {
+ case 'show':
+ setIsVisible(true);
+ return;
+ case 'hide':
+ setIsVisible(false);
+ return;
+ case 'toggle':
+ setIsVisible(prevValue => !prevValue);
+ return;
+ case 'tab':
+ if(parts.length > 2)
+ {
+ const tab = parts[2] as WiredToolsTab;
+
+ if(TABS.some(entry => entry.key === tab)) setActiveTab(tab);
+ }
+ setIsVisible(true);
+ return;
+ }
+ },
+ eventUrlPrefix: 'wired-tools/'
+ };
+
+ AddLinkEventTracker(linkTracker);
+
+ return () => RemoveLinkEventTracker(linkTracker);
+ }, []);
+
+ const currentTabLabel = useMemo(() => TABS.find(tab => tab.key === activeTab)?.label ?? 'Monitor', [ activeTab ]);
+
+ if(!isVisible) return null;
+
+ return (
+
+ setIsVisible(false) } />
+
+ { TABS.map(tab => (
+ setActiveTab(tab.key) }>
+ { tab.label }
+
+ )) }
+
+
+
+
+ { currentTabLabel }
+
+ { (activeTab === 'monitor') &&
+
+
+ This is the initial shell for the Wired Creator Tools. We can now build the real functionality tab by tab.
+
+
+
+
Statistics:
+ { MONITOR_STATS.map(stat => (
+
+ { stat.label }:
+ { stat.value }
+
+ )) }
+
+
+
+
Monitor Preview
+
+ Live statistics, executor health and diagnostics can be connected here next.
+
+
+
+
+
+
Logs:
+
+
+
+
+ | Type |
+ Category |
+ Amount |
+ Latest occurrence |
+
+
+
+ { MONITOR_LOGS.map((log, index) => (
+
+ | { log.type } |
+ { log.category } |
+ { log.amount } |
+ { log.latest } |
+
+ )) }
+
+
+
+
+
+
+
+
+
}
+ { (activeTab !== 'monitor') &&
+
+
+
{ currentTabLabel }
+
+ This tab is now ready to be wired into the new `:wired` tools flow.
+
+
+
}
+
+
+
+ );
+};
diff --git a/src/components/wired/views/WiredBaseView.tsx b/src/components/wired/views/WiredBaseView.tsx
index 4c4f0db..f9477af 100644
--- a/src/components/wired/views/WiredBaseView.tsx
+++ b/src/components/wired/views/WiredBaseView.tsx
@@ -24,7 +24,11 @@ export const WiredBaseView: FC> = props =>
const [ needsSave, setNeedsSave ] = useState(false);
const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired();
- const onClose = () => setTrigger(null);
+ const onClose = () =>
+ {
+ WiredSelectionVisualizer.clearAllSelectionShaders();
+ setTrigger(null);
+ };
const onSave = () =>
{
@@ -48,6 +52,8 @@ export const WiredBaseView: FC> = props =>
{
if(!trigger) return;
+ WiredSelectionVisualizer.clearAllSelectionShaders();
+
const spriteId = (trigger.spriteId || -1);
const furniData = GetSessionDataManager().getFloorItemData(spriteId);
diff --git a/src/components/wired/views/WiredSourcesSelector.tsx b/src/components/wired/views/WiredSourcesSelector.tsx
index db072a6..2d5ebc1 100644
--- a/src/components/wired/views/WiredSourcesSelector.tsx
+++ b/src/components/wired/views/WiredSourcesSelector.tsx
@@ -16,45 +16,66 @@ export const USER_SOURCES = [
{ value: 201, label: 'wiredfurni.params.sources.users.201' }
];
+export interface WiredSourceOption
+{
+ value: number;
+ label: string;
+}
+
interface WiredSourcesSelectorProps
{
showFurni?: boolean;
showUsers?: boolean;
furniSource?: number;
userSource?: number;
+ furniTitle?: string;
+ usersTitle?: string;
+ furniSources?: WiredSourceOption[];
+ userSources?: WiredSourceOption[];
onChangeFurni?: (source: number) => void;
onChangeUsers?: (source: number) => void;
}
export const WiredSourcesSelector: FC = props =>
{
- const { showFurni = false, showUsers = false, furniSource = 0, userSource = 0, onChangeFurni = null, onChangeUsers = null } = props;
+ const {
+ showFurni = false,
+ showUsers = false,
+ furniSource = 0,
+ userSource = 0,
+ furniTitle = 'wiredfurni.params.sources.furni.title',
+ usersTitle = 'wiredfurni.params.sources.users.title',
+ furniSources = FURNI_SOURCES,
+ userSources = USER_SOURCES,
+ onChangeFurni = null,
+ onChangeUsers = null
+ } = props;
- const furniIndex = Math.max(0, FURNI_SOURCES.findIndex(s => s.value === furniSource));
- const userIndex = Math.max(0, USER_SOURCES.findIndex(s => s.value === userSource));
+ const furniIndex = Math.max(0, furniSources.findIndex(s => s.value === furniSource));
+ const userIndex = Math.max(0, userSources.findIndex(s => s.value === userSource));
const prevFurni = () =>
{
- const next = (furniIndex - 1 + FURNI_SOURCES.length) % FURNI_SOURCES.length;
- onChangeFurni && onChangeFurni(FURNI_SOURCES[next].value);
+ const next = (furniIndex - 1 + furniSources.length) % furniSources.length;
+ onChangeFurni && onChangeFurni(furniSources[next].value);
};
const nextFurni = () =>
{
- const next = (furniIndex + 1) % FURNI_SOURCES.length;
- onChangeFurni && onChangeFurni(FURNI_SOURCES[next].value);
+ const next = (furniIndex + 1) % furniSources.length;
+ onChangeFurni && onChangeFurni(furniSources[next].value);
};
const prevUsers = () =>
{
- const next = (userIndex - 1 + USER_SOURCES.length) % USER_SOURCES.length;
- onChangeUsers && onChangeUsers(USER_SOURCES[next].value);
+ const next = (userIndex - 1 + userSources.length) % userSources.length;
+ onChangeUsers && onChangeUsers(userSources[next].value);
};
const nextUsers = () =>
{
- const next = (userIndex + 1) % USER_SOURCES.length;
- onChangeUsers && onChangeUsers(USER_SOURCES[next].value);
+ const next = (userIndex + 1) % userSources.length;
+ onChangeUsers && onChangeUsers(userSources[next].value);
};
if(!showFurni && !showUsers) return null;
@@ -63,11 +84,11 @@ export const WiredSourcesSelector: FC = props =>
{ showFurni &&
<>
-
{ LocalizeText('wiredfurni.params.sources.furni.title') }
+
{ LocalizeText(furniTitle) }
- { LocalizeText(FURNI_SOURCES[furniIndex].label) }
+ { LocalizeText(furniSources[furniIndex].label) }
@@ -77,11 +98,11 @@ export const WiredSourcesSelector: FC
= props =>
{ showUsers &&
<>
- { LocalizeText('wiredfurni.params.sources.users.title') }
+ { LocalizeText(usersTitle) }
- { LocalizeText(USER_SOURCES[userIndex].label) }
+ { LocalizeText(userSources[userIndex].label) }
@@ -89,4 +110,3 @@ export const WiredSourcesSelector: FC = props =>
);
};
-
diff --git a/src/components/wired/views/actions/WiredActionFreezeView.tsx b/src/components/wired/views/actions/WiredActionFreezeView.tsx
new file mode 100644
index 0000000..716acaa
--- /dev/null
+++ b/src/components/wired/views/actions/WiredActionFreezeView.tsx
@@ -0,0 +1,54 @@
+import { FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredActionBaseView } from './WiredActionBaseView';
+import { WiredSourcesSelector } from '../WiredSourcesSelector';
+
+const EFFECT_OPTIONS = [
+ { value: 218, label: 'fx_218' },
+ { value: 12, label: 'fx_12' },
+ { value: 11, label: 'fx_11' },
+ { value: 53, label: 'fx_53' },
+ { value: 163, label: 'fx_163' }
+];
+
+export const WiredActionFreezeView: FC<{}> = () =>
+{
+ const [ effectId, setEffectId ] = useState(218);
+ const [ cancelOnTeleport, setCancelOnTeleport ] = useState(false);
+ const [ userSource, setUserSource ] = useState(0);
+ const { trigger = null, setIntParams = null } = useWired();
+
+ const save = () => setIntParams([
+ effectId,
+ cancelOnTeleport ? 1 : 0,
+ userSource
+ ]);
+
+ useEffect(() =>
+ {
+ setEffectId((trigger?.intData?.length > 0) ? trigger.intData[0] : 218);
+ setCancelOnTeleport((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
+ setUserSource((trigger?.intData?.length > 2) ? trigger.intData[2] : 0);
+ }, [ trigger ]);
+
+ return (
+ }>
+
+ Effect
+
+
+
+ setCancelOnTeleport(event.target.checked) } />
+ { LocalizeText('wiredfurni.params.freeze.cancel_on_teleport') }
+
+
+ );
+};
diff --git a/src/components/wired/views/actions/WiredActionFurniToFurniView.tsx b/src/components/wired/views/actions/WiredActionFurniToFurniView.tsx
new file mode 100644
index 0000000..5f7cefc
--- /dev/null
+++ b/src/components/wired/views/actions/WiredActionFurniToFurniView.tsx
@@ -0,0 +1,222 @@
+import { FC, useCallback, useEffect, useRef, useState } from 'react';
+import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api';
+import { Button, Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredSourcesSelector, FURNI_SOURCES, WiredSourceOption } from '../WiredSourcesSelector';
+import { WiredActionBaseView } from './WiredActionBaseView';
+
+const SOURCE_TRIGGER = 0;
+const SOURCE_SELECTED = 100;
+const SOURCE_SECONDARY_SELECTED = 101;
+const FURNI_DELIMITER = ';';
+
+const TARGET_FURNI_SOURCES: WiredSourceOption[] = [
+ { value: 0, label: 'wiredfurni.params.sources.furni.0' },
+ { value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' },
+ { value: 200, label: 'wiredfurni.params.sources.furni.200' },
+ { value: 201, label: 'wiredfurni.params.sources.furni.201' }
+];
+
+type SelectionMode = 'move' | 'target';
+
+const parseIds = (data: string): number[] =>
+{
+ if(!data || !data.length) return [];
+
+ const ids = new Set();
+
+ for(const part of data.split(/[;,\t]/))
+ {
+ const trimmed = part.trim();
+ if(!trimmed.length) continue;
+
+ const value = parseInt(trimmed, 10);
+ if(!isNaN(value) && value > 0) ids.add(value);
+ }
+
+ return Array.from(ids);
+};
+
+const serializeIds = (ids: number[]): string =>
+{
+ if(!ids || !ids.length) return '';
+
+ return ids.filter(id => (id > 0)).join(FURNI_DELIMITER);
+};
+
+export const WiredActionFurniToFurniView: FC<{}> = () =>
+{
+ const [ moveSource, setMoveSource ] = useState(SOURCE_TRIGGER);
+ const [ targetSource, setTargetSource ] = useState(SOURCE_TRIGGER);
+ const [ moveFurniIds, setMoveFurniIds ] = useState([]);
+ const [ targetFurniIds, setTargetFurniIds ] = useState([]);
+ const [ selectionMode, setSelectionMode ] = useState('move');
+
+ const highlightedIds = useRef([]);
+
+ const { trigger = null, furniIds = [], setFurniIds, setIntParams, setStringParam, setAllowsFurni } = useWired();
+
+ const syncHighlights = useCallback((nextMoveIds: number[], nextTargetIds: number[]) =>
+ {
+ if(highlightedIds.current.length)
+ {
+ WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
+ WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
+ }
+
+ const targetSet = new Set(nextTargetIds);
+ const moveOnlyIds = nextMoveIds.filter(id => !targetSet.has(id));
+
+ if(moveOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(moveOnlyIds);
+ if(nextTargetIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextTargetIds);
+
+ highlightedIds.current = Array.from(new Set([ ...nextMoveIds, ...nextTargetIds ]));
+ }, []);
+
+ const switchSelection = useCallback((mode: SelectionMode) =>
+ {
+ const canEditMove = (moveSource === SOURCE_SELECTED);
+ const canEditTarget = (targetSource === SOURCE_SECONDARY_SELECTED);
+
+ if(mode === 'move' && !canEditMove) return;
+ if(mode === 'target' && !canEditTarget) return;
+
+ setSelectionMode(mode);
+ setFurniIds([ ...(mode === 'move' ? moveFurniIds : targetFurniIds) ]);
+ }, [ moveSource, targetSource, moveFurniIds, targetFurniIds, setFurniIds ]);
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ const nextMoveIds = trigger.selectedItems ?? [];
+ const nextTargetIds = parseIds(trigger.stringData);
+ const nextMoveSource = (trigger.intData.length >= 1)
+ ? trigger.intData[0]
+ : (nextMoveIds.length ? SOURCE_SELECTED : SOURCE_TRIGGER);
+ const nextTargetSourceRaw = (trigger.intData.length >= 2)
+ ? trigger.intData[1]
+ : (nextTargetIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER);
+ const nextTargetSource = (nextTargetSourceRaw === SOURCE_SELECTED) ? SOURCE_SECONDARY_SELECTED : nextTargetSourceRaw;
+
+ setMoveSource(nextMoveSource);
+ setTargetSource(nextTargetSource);
+ setMoveFurniIds(nextMoveIds);
+ setTargetFurniIds(nextTargetIds);
+ setSelectionMode('move');
+ setFurniIds([ ...nextMoveIds ]);
+ }, [ trigger, setFurniIds ]);
+
+ useEffect(() =>
+ {
+ if(selectionMode === 'move') setMoveFurniIds(furniIds);
+ else setTargetFurniIds(furniIds);
+ }, [ furniIds, selectionMode ]);
+
+ useEffect(() =>
+ {
+ syncHighlights(moveFurniIds, targetFurniIds);
+ }, [ moveFurniIds, targetFurniIds, syncHighlights ]);
+
+ useEffect(() =>
+ {
+ const canEditMove = (moveSource === SOURCE_SELECTED);
+ const canEditTarget = (targetSource === SOURCE_SECONDARY_SELECTED);
+
+ if(selectionMode === 'move' && !canEditMove && canEditTarget)
+ {
+ switchSelection('target');
+ return;
+ }
+
+ if(selectionMode === 'target' && !canEditTarget && canEditMove)
+ {
+ switchSelection('move');
+ return;
+ }
+
+ const canEditCurrent = ((selectionMode === 'move') ? canEditMove : canEditTarget);
+ setAllowsFurni(canEditCurrent ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE);
+ }, [ selectionMode, moveSource, targetSource, switchSelection, setAllowsFurni ]);
+
+ useEffect(() =>
+ {
+ return () =>
+ {
+ if(!highlightedIds.current.length) return;
+
+ WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current);
+ WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current);
+ highlightedIds.current = [];
+ };
+ }, []);
+
+ const save = useCallback(() =>
+ {
+ if(selectionMode === 'target')
+ {
+ setSelectionMode('move');
+ setFurniIds([ ...moveFurniIds ]);
+ }
+
+ setIntParams([
+ moveSource,
+ targetSource
+ ]);
+
+ setStringParam(serializeIds(targetFurniIds));
+ }, [ selectionMode, moveFurniIds, moveSource, targetSource, targetFurniIds, setFurniIds, setIntParams, setStringParam ]);
+
+ const selectionLimit = trigger?.maximumItemSelectionCount ?? 0;
+
+ return (
+
+
+
+ setTargetSource((value === SOURCE_SELECTED) ? SOURCE_SECONDARY_SELECTED : value) } />
+
+ }>
+
+
+
{ LocalizeText('wiredfurni.params.sources.furni.title.mv.0') }
+
+
+ { selectionLimit ? `${ moveFurniIds.length }/${ selectionLimit }` : moveFurniIds.length }
+
+
+
+
{ LocalizeText('wiredfurni.params.sources.furni.title.mv.1') }
+
+
+ { selectionLimit ? `${ targetFurniIds.length }/${ selectionLimit }` : targetFurniIds.length }
+
+
+
+
+ );
+};
diff --git a/src/components/wired/views/actions/WiredActionGiveRewardView.tsx b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx
index 91c1468..3fb239a 100644
--- a/src/components/wired/views/actions/WiredActionGiveRewardView.tsx
+++ b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx
@@ -7,6 +7,131 @@ import { NitroInput } from '../../../../layout';
import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector';
+type RewardType = 'badge' | 'credits' | 'pixels' | 'diamonds' | 'points' | 'furni' | 'respect';
+
+interface RewardEntry
+{
+ rewardType: RewardType;
+ rewardValue: string;
+ probability: number;
+ pointsType: number;
+}
+
+const DEFAULT_PROBABILITY = 100;
+const DEFAULT_POINTS_TYPE = 5;
+
+const REWARD_TYPES: { value: RewardType, label: string }[] = [
+ { value: 'badge', label: 'Badge' },
+ { value: 'credits', label: 'Credits' },
+ { value: 'pixels', label: 'Pixels / Duckets' },
+ { value: 'diamonds', label: 'Diamonds' },
+ { value: 'points', label: 'Extra Currency' },
+ { value: 'furni', label: 'Furni' },
+ { value: 'respect', label: 'Respect' }
+];
+
+const SELECTABLE_REWARD_TYPES = REWARD_TYPES.filter(entry => (entry.value !== 'respect'));
+
+const createReward = (): RewardEntry =>
+({
+ rewardType: 'furni',
+ rewardValue: '',
+ probability: DEFAULT_PROBABILITY,
+ pointsType: DEFAULT_POINTS_TYPE
+});
+
+const getRewardValuePlaceholder = (rewardType: RewardType) =>
+{
+ switch(rewardType)
+ {
+ case 'badge':
+ return 'Badge code';
+ case 'credits':
+ return 'Credits amount';
+ case 'pixels':
+ return 'Pixels amount';
+ case 'diamonds':
+ return 'Diamonds amount';
+ case 'points':
+ return 'Amount';
+ case 'furni':
+ return 'Furni base item id';
+ case 'respect':
+ return 'Respect amount';
+ }
+};
+
+const getExtraFieldLabel = (rewardType: RewardType) =>
+{
+ switch(rewardType)
+ {
+ case 'points':
+ return 'Currency Type';
+ case 'badge':
+ return 'Code';
+ default:
+ return 'Info';
+ }
+};
+
+const getExtraFieldPlaceholder = (rewardType: RewardType) =>
+{
+ switch(rewardType)
+ {
+ case 'points':
+ return 'Type id (e.g. 105)';
+ case 'badge':
+ return 'Badge';
+ default:
+ return '';
+ }
+};
+
+const parseRewardEntry = (rawType: string, rawCode: string, rawProbability: string): RewardEntry =>
+{
+ const probability = Number(rawProbability);
+ const parsedProbability = Number.isFinite(probability) ? probability : DEFAULT_PROBABILITY;
+
+ if(rawType === '0')
+ {
+ return { rewardType: 'badge', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
+ }
+
+ const separatorIndex = rawCode.indexOf('#');
+
+ if(separatorIndex === -1)
+ {
+ return { rewardType: 'furni', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
+ }
+
+ const rewardType = rawCode.slice(0, separatorIndex);
+ const rewardValue = rawCode.slice(separatorIndex + 1);
+
+ if(rewardType.startsWith('points'))
+ {
+ const pointsType = Number(rewardType.slice('points'.length));
+
+ return {
+ rewardType: 'points',
+ rewardValue,
+ probability: parsedProbability,
+ pointsType: Number.isFinite(pointsType) ? pointsType : DEFAULT_POINTS_TYPE
+ };
+ }
+
+ if(REWARD_TYPES.some(entry => (entry.value === rewardType)))
+ {
+ return { rewardType: rewardType as RewardType, rewardValue, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
+ }
+
+ if(rewardType === 'cata')
+ {
+ return { rewardType: 'furni', rewardValue, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
+ }
+
+ return { rewardType: 'furni', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
+};
+
export const WiredActionGiveRewardView: FC<{}> = props =>
{
const [ limitEnabled, setLimitEnabled ] = useState(false);
@@ -14,7 +139,7 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
const [ uniqueRewards, setUniqueRewards ] = useState(false);
const [ rewardsLimit, setRewardsLimit ] = useState(1);
const [ limitationInterval, setLimitationInterval ] = useState(1);
- const [ rewards, setRewards ] = useState<{ isBadge: boolean, itemCode: string, probability: number }[]>([]);
+ const [ rewards, setRewards ] = useState
([]);
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ userSource, setUserSource ] = useState(() =>
{
@@ -22,7 +147,8 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
return 0;
});
- const addReward = () => setRewards(rewards => [ ...rewards, { isBadge: false, itemCode: '', probability: null } ]);
+ const addReward = () => setRewards(rewards => [ ...rewards, createReward() ]);
+ const hasCustomCurrencyReward = rewards.some(reward => (reward.rewardType === 'points'));
const removeReward = (index: number) =>
{
@@ -36,18 +162,9 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
});
};
- const updateReward = (index: number, isBadge: boolean, itemCode: string, probability: number) =>
+ const updateReward = (index: number, updater: (reward: RewardEntry) => RewardEntry) =>
{
- const rewardsClone = Array.from(rewards);
- const reward = rewardsClone[index];
-
- if(!reward) return;
-
- reward.isBadge = isBadge;
- reward.itemCode = itemCode;
- reward.probability = probability;
-
- setRewards(rewardsClone);
+ setRewards(prevValue => prevValue.map((reward, rewardIndex) => ((rewardIndex === index) ? updater(reward) : reward)));
};
const save = () =>
@@ -56,9 +173,20 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
for(const reward of rewards)
{
- if(!reward.itemCode) continue;
+ const rewardValue = reward.rewardValue.trim();
- const rewardsString = [ reward.isBadge ? '0' : '1', reward.itemCode, reward.probability.toString() ];
+ if(!rewardValue) continue;
+
+ const probability = Math.max(0, Number.isFinite(reward.probability) ? reward.probability : DEFAULT_PROBABILITY);
+ const rewardCode = (() =>
+ {
+ if(reward.rewardType === 'badge') return rewardValue;
+ if(reward.rewardType === 'points') return `points${ Math.max(0, reward.pointsType) }#${ rewardValue }`;
+
+ return `${ reward.rewardType }#${ rewardValue }`;
+ })();
+
+ const rewardsString = [ reward.rewardType === 'badge' ? '0' : '1', rewardCode, (uniqueRewards ? DEFAULT_PROBABILITY : probability).toString() ];
stringRewards.push(rewardsString.join(','));
}
@@ -71,9 +199,9 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
useEffect(() =>
{
- const readRewards: { isBadge: boolean, itemCode: string, probability: number }[] = [];
+ const readRewards: RewardEntry[] = [];
- if(trigger.stringData.length > 0 && trigger.stringData.includes(';'))
+ if(trigger.stringData.length > 0)
{
const splittedRewards = trigger.stringData.split(';');
@@ -83,11 +211,11 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
if(reward.length !== 3) continue;
- readRewards.push({ isBadge: reward[0] === '0', itemCode: reward[1], probability: Number(reward[2]) });
+ readRewards.push(parseRewardEntry(reward[0], reward[1], reward[2]));
}
}
- if(readRewards.length === 0) readRewards.push({ isBadge: false, itemCode: '', probability: null });
+ if(readRewards.length === 0) readRewards.push(createReward());
setRewardTime((trigger.intData.length > 0) ? trigger.intData[0] : 0);
setUniqueRewards((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false);
@@ -147,24 +275,64 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
+
+ Type
+ Amount / Value
+ { uniqueRewards ? 'Mode' : 'Chance %' }
+ { hasCustomCurrencyReward ? 'Currency Type' : 'Extra / Info' }
+ Action
+
{ rewards && rewards.map((reward, index) =>
{
+ const rewardTypeOptions = (reward.rewardType === 'respect')
+ ? REWARD_TYPES
+ : SELECTABLE_REWARD_TYPES;
+
return (
-
-
-
updateReward(index, e.target.checked, reward.itemCode, reward.probability) } />
-
Badge?
+
+
+
updateReward(index, prevValue => ({ ...prevValue, rewardValue: event.target.value })) } />
+ { uniqueRewards
+ ?
+ Unique
+
+ : updateReward(index, prevValue => ({ ...prevValue, probability: Number(event.target.value) })) } /> }
+ { (reward.rewardType === 'points')
+ ?
+ updateReward(index, prevValue => ({ ...prevValue, pointsType: Number(event.target.value) })) } />
+ :
+ { getExtraFieldLabel(reward.rewardType) }
+
}
+
+ { (index > 0) &&
+ }
- updateReward(index, reward.isBadge, e.target.value, reward.probability) } />
- updateReward(index, reward.isBadge, reward.itemCode, Number(e.target.value)) } />
- { (index > 0) &&
- }
);
}) }
+
+ Extra Currency uses Amount as the quantity and Currency Type as the purse type id. Example: amount 200 + type 105.
+
);
};
diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx
index 23c16d1..551da1a 100644
--- a/src/components/wired/views/actions/WiredActionLayoutView.tsx
+++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx
@@ -1,5 +1,7 @@
import { WiredActionLayoutCode } from '../../../../api';
import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView';
+import { WiredActionFreezeView } from './WiredActionFreezeView';
+import { WiredActionFurniToFurniView } from './WiredActionFurniToFurniView';
import { WiredActionSendSignalView } from './WiredActionSendSignalView';
import { WiredActionFurniAreaView } from '../selectors/WiredActionFurniAreaView';
import { WiredSelectorFurniNeighborhoodView } from '../selectors/WiredSelectorFurniNeighborhoodView';
@@ -26,10 +28,12 @@ import { WiredActionMoveAndRotateFurniView } from './WiredActionMoveAndRotateFur
import { WiredActionMoveFurniToView } from './WiredActionMoveFurniToView';
import { WiredActionMoveFurniView } from './WiredActionMoveFurniView';
import { WiredActionMuteUserView } from './WiredActionMuteUserView';
+import { WiredActionRelativeMoveView } from './WiredActionRelativeMoveView';
import { WiredActionResetView } from './WiredActionResetView';
import { WiredActionSetFurniStateToView } from './WiredActionSetFurniStateToView';
import { WiredActionTeleportView } from './WiredActionTeleportView';
import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView';
+import { WiredActionUnfreezeView } from './WiredActionUnfreezeView';
export const WiredActionLayoutView = (code: number) =>
{
@@ -57,6 +61,12 @@ export const WiredActionLayoutView = (code: number) =>
return
;
case WiredActionLayoutCode.FLEE:
return
;
+ case WiredActionLayoutCode.FREEZE:
+ return
;
+ case WiredActionLayoutCode.FURNI_TO_USER:
+ return
;
+ case WiredActionLayoutCode.FURNI_TO_FURNI:
+ return
;
case WiredActionLayoutCode.GIVE_REWARD:
return
;
case WiredActionLayoutCode.GIVE_SCORE:
@@ -77,6 +87,8 @@ export const WiredActionLayoutView = (code: number) =>
return
;
case WiredActionLayoutCode.MUTE_USER:
return
;
+ case WiredActionLayoutCode.RELATIVE_MOVE:
+ return
;
case WiredActionLayoutCode.RESET:
return
;
case WiredActionLayoutCode.SET_FURNI_STATE:
@@ -85,6 +97,10 @@ export const WiredActionLayoutView = (code: number) =>
return
;
case WiredActionLayoutCode.TOGGLE_FURNI_STATE:
return
;
+ case WiredActionLayoutCode.UNFREEZE:
+ return
;
+ case WiredActionLayoutCode.USER_TO_FURNI:
+ return
;
case WiredActionLayoutCode.FURNI_AREA_SELECTOR:
return
;
case WiredActionLayoutCode.FURNI_NEIGHBORHOOD_SELECTOR:
diff --git a/src/components/wired/views/actions/WiredActionRelativeMoveView.tsx b/src/components/wired/views/actions/WiredActionRelativeMoveView.tsx
new file mode 100644
index 0000000..c6f33a1
--- /dev/null
+++ b/src/components/wired/views/actions/WiredActionRelativeMoveView.tsx
@@ -0,0 +1,120 @@
+import { FC, useEffect, useState } from 'react';
+import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Slider, Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredSourcesSelector } from '../WiredSourcesSelector';
+import { WiredActionBaseView } from './WiredActionBaseView';
+
+const MAX_DISTANCE = 20;
+
+const HORIZONTAL_OPTIONS = [
+ { value: 0, icon:
},
+ { value: 1, icon:
}
+];
+
+const VERTICAL_OPTIONS = [
+ { value: 0, icon:
},
+ { value: 1, icon:
}
+];
+
+const normalizeDirection = (value: number, fallback = 1) =>
+{
+ if(value === 0 || value === 1) return value;
+
+ return fallback;
+};
+
+const normalizeDistance = (value: number) =>
+{
+ if(isNaN(value)) return 0;
+
+ return Math.max(0, Math.min(MAX_DISTANCE, value));
+};
+
+export const WiredActionRelativeMoveView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null } = useWired();
+
+ const [horizontalDirection, setHorizontalDirection] = useState(1);
+ const [horizontalDistance, setHorizontalDistance] = useState(0);
+ const [verticalDirection, setVerticalDirection] = useState(1);
+ const [verticalDistance, setVerticalDistance] = useState(0);
+ const [ furniSource, setFurniSource ] = useState
(() =>
+ {
+ if(trigger?.intData?.length > 4) return trigger.intData[4];
+ return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
+ });
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ setHorizontalDirection((trigger.intData.length > 0) ? normalizeDirection(trigger.intData[0], 1) : 1);
+ setHorizontalDistance((trigger.intData.length > 1) ? normalizeDistance(trigger.intData[1]) : 0);
+ setVerticalDirection((trigger.intData.length > 2) ? normalizeDirection(trigger.intData[2], 1) : 1);
+ setVerticalDistance((trigger.intData.length > 3) ? normalizeDistance(trigger.intData[3]) : 0);
+
+ if(trigger.intData.length > 4) setFurniSource(trigger.intData[4]);
+ else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
+ }, [ trigger ]);
+
+ const save = () => setIntParams([
+ horizontalDirection,
+ horizontalDistance,
+ verticalDirection,
+ verticalDistance,
+ furniSource
+ ]);
+
+ return (
+ }>
+
+
{ LocalizeText('wiredfurni.params.movement.horizontal.selection') }
+
+ { HORIZONTAL_OPTIONS.map(option =>
+ {
+ return (
+
+ );
+ }) }
+
+
{ LocalizeText('wiredfurni.params.movement.horizontal.distance', [ 'distance' ], [ horizontalDistance.toString() ]) }
+
setHorizontalDistance(value as number) } />
+
+
+
{ LocalizeText('wiredfurni.params.movement.vertical.selection') }
+
+ { VERTICAL_OPTIONS.map(option =>
+ {
+ return (
+
+ );
+ }) }
+
+
{ LocalizeText('wiredfurni.params.movement.vertical.distance', [ 'distance' ], [ verticalDistance.toString() ]) }
+
setVerticalDistance(value as number) } />
+
+
+ );
+};
diff --git a/src/components/wired/views/actions/WiredActionSetAltitudeView.tsx b/src/components/wired/views/actions/WiredActionSetAltitudeView.tsx
new file mode 100644
index 0000000..3e4017b
--- /dev/null
+++ b/src/components/wired/views/actions/WiredActionSetAltitudeView.tsx
@@ -0,0 +1,162 @@
+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 MIN_ALTITUDE = 0;
+const MAX_ALTITUDE = 40;
+const ALTITUDE_STEP = 0.01;
+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);
+};
+
+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;
+};
+
+export const WiredActionSetAltitudeView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
+
+ const [ operator, setOperator ] = useState(2);
+ const [ furniSource, setFurniSource ] = useState(() =>
+ {
+ if(trigger?.intData?.length > 1) return trigger.intData[1];
+ return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
+ });
+ const [ altitude, setAltitude ] = useState(0);
+ const [ altitudeInput, setAltitudeInput ] = useState('0');
+
+ const normalizedAltitudeText = useMemo(() => formatAltitude(altitude), [ altitude ]);
+
+ 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));
+
+ const nextAltitude = parseAltitude(trigger.stringData);
+ 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 = () =>
+ {
+ setIntParams([
+ operator,
+ furniSource
+ ]);
+
+ setStringParam(normalizedAltitudeText);
+ };
+
+ return (
+ }>
+
+ { OPERATOR_OPTIONS.map(option =>
+ {
+ return (
+
+ setOperator(option.value) } />
+ { LocalizeText(option.label) }
+
+ );
+ }) }
+
+
+ { LocalizeText('wiredfurni.params.setaltitude') }
+ setAltitudeInput(formatAltitude(altitude)) }
+ onChange={ event => updateAltitudeInput(event.target.value) } />
+
+
+ updateAltitude(event as number) } />
+ { normalizedAltitudeText }
+
+
+ );
+};
diff --git a/src/components/wired/views/actions/WiredActionUnfreezeView.tsx b/src/components/wired/views/actions/WiredActionUnfreezeView.tsx
new file mode 100644
index 0000000..98b31a6
--- /dev/null
+++ b/src/components/wired/views/actions/WiredActionUnfreezeView.tsx
@@ -0,0 +1,26 @@
+import { FC, useEffect, useState } from 'react';
+import { WiredFurniType } from '../../../../api';
+import { useWired } from '../../../../hooks';
+import { WiredActionBaseView } from './WiredActionBaseView';
+import { WiredSourcesSelector } from '../WiredSourcesSelector';
+
+export const WiredActionUnfreezeView: FC<{}> = () =>
+{
+ const [ userSource, setUserSource ] = useState(0);
+ const { trigger = null, setIntParams = null } = useWired();
+
+ const save = () => setIntParams([ userSource ]);
+
+ useEffect(() =>
+ {
+ setUserSource((trigger?.intData?.length > 0) ? trigger.intData[0] : 0);
+ }, [ trigger ]);
+
+ return (
+ } />
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx b/src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx
new file mode 100644
index 0000000..91a6a82
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx
@@ -0,0 +1,185 @@
+import { FC, useEffect, 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 MIN_ALTITUDE = 0;
+const MAX_ALTITUDE = 40;
+const ALTITUDE_STEP = 0.01;
+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 WiredConditionHasAltitudeView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null, setStringParam = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired();
+ const [ comparison, setComparison ] = useState(1);
+ const [ furniSource, setFurniSource ] = useState(() =>
+ {
+ if(trigger?.intData?.length > 1) return trigger.intData[1];
+ return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
+ });
+ const [ quantifier, setQuantifier ] = useState(0);
+ const [ showAdvanced, setShowAdvanced ] = useState(false);
+ const [ altitude, setAltitude ] = useState(0);
+ const [ altitudeInput, setAltitudeInput ] = useState('0');
+
+ useEffect(() =>
+ {
+ setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES);
+ setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni');
+
+ return () =>
+ {
+ setAllowedInteractionTypes(null);
+ setAllowedInteractionErrorKey(null);
+ };
+ }, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]);
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ setComparison((trigger.intData.length > 0) ? trigger.intData[0] : 1);
+ setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0));
+ setQuantifier((trigger.intData.length > 2) ? trigger.intData[2] : 0);
+ setShowAdvanced((trigger.intData.length > 1) ? (trigger.intData[1] !== 0 || trigger.intData[2] !== 0) : false);
+
+ const nextAltitude = parseAltitude(trigger.stringData);
+ 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 = () =>
+ {
+ setIntParams([
+ comparison,
+ furniSource,
+ quantifier
+ ]);
+ setStringParam(formatAltitude(altitude));
+ };
+
+ return (
+
+
+ { showAdvanced &&
+ <>
+
+
{ LocalizeText('wiredfurni.params.quantifier_selection') }
+ { [ 0, 1 ].map(value =>
+ {
+ return (
+
+ setQuantifier(value) } />
+ { LocalizeText(`wiredfurni.params.quantifier.furni.${ value }`) }
+
+ );
+ }) }
+
+
+ > }
+
+ }>
+
+ { [ 0, 1, 2 ].map(value =>
+ {
+ return (
+
+ setComparison(value) } />
+ { LocalizeText(`wiredfurni.params.comparison.${ value }`) }
+
+ );
+ }) }
+
+
+ { LocalizeText('wiredfurni.params.setaltitude') }
+ setAltitudeInput(formatAltitude(altitude)) }
+ onChange={ event => updateAltitudeInput(event.target.value) } />
+
+
+ updateAltitude(event as number) } />
+ { formatAltitude(altitude) }
+
+
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx
index a1a88c2..888df16 100644
--- a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx
+++ b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx
@@ -5,7 +5,11 @@ import { WiredConditionActorIsOnFurniView } from './WiredConditionActorIsOnFurni
import { WiredConditionActorIsTeamMemberView } from './WiredConditionActorIsTeamMemberView';
import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWearingBadgeView';
import { WiredConditionActorIsWearingEffectView } from './WiredConditionActorIsWearingEffectView';
+import { WiredConditionCounterTimeMatchesView } from './WiredConditionCounterTimeMatchesView';
import { WiredConditionDateRangeView } from './WiredConditionDateRangeView';
+import { WiredConditionMatchDateView } from './WiredConditionMatchDateView';
+import { WiredConditionMatchTimeView } from './WiredConditionMatchTimeView';
+import { WiredConditionHasAltitudeView } from './WiredConditionHasAltitudeView';
import { WiredConditionFurniHasAvatarOnView } from './WiredConditionFurniHasAvatarOnView';
import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurniOnView';
import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView';
@@ -13,6 +17,10 @@ import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeVi
import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView';
import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView';
import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView';
+import { WiredConditionTeamHasRankView } from './WiredConditionTeamHasRankView';
+import { WiredConditionTeamHasScoreView } from './WiredConditionTeamHasScoreView';
+import { WiredConditionTriggererMatchView } from './WiredConditionTriggererMatchView';
+import { WiredConditionUserPerformsActionView } from './WiredConditionUserPerformsActionView';
import { WiredConditionUserCountInRoomView } from './WiredConditionUserCountInRoomView';
export const WiredConditionLayoutView = (code: number) =>
@@ -20,7 +28,11 @@ export const WiredConditionLayoutView = (code: number) =>
switch(code)
{
case WiredConditionlayout.ACTOR_HAS_HANDITEM:
+ case WiredConditionlayout.NOT_ACTOR_HAS_HANDITEM:
return
;
+ case WiredConditionlayout.TRIGGERER_MATCH:
+ case WiredConditionlayout.NOT_TRIGGERER_MATCH:
+ return
;
case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER:
case WiredConditionlayout.NOT_ACTOR_IN_GROUP:
return
;
@@ -38,6 +50,10 @@ export const WiredConditionLayoutView = (code: number) =>
return
;
case WiredConditionlayout.DATE_RANGE_ACTIVE:
return
;
+ case WiredConditionlayout.MATCH_TIME:
+ return
;
+ case WiredConditionlayout.MATCH_DATE:
+ return
;
case WiredConditionlayout.FURNIS_HAVE_AVATARS:
case WiredConditionlayout.FURNI_NOT_HAVE_HABBO:
return
;
@@ -58,6 +74,18 @@ export const WiredConditionLayoutView = (code: number) =>
case WiredConditionlayout.USER_COUNT_IN:
case WiredConditionlayout.NOT_USER_COUNT_IN:
return
;
+ case WiredConditionlayout.COUNTER_TIME_MATCHES:
+ return
;
+ case WiredConditionlayout.USER_PERFORMS_ACTION:
+ return
;
+ case WiredConditionlayout.NOT_USER_PERFORMS_ACTION:
+ return
;
+ case WiredConditionlayout.HAS_ALTITUDE:
+ return
;
+ case WiredConditionlayout.TEAM_HAS_SCORE:
+ return
;
+ case WiredConditionlayout.TEAM_HAS_RANK:
+ return
;
}
return null;
diff --git a/src/components/wired/views/conditions/WiredConditionMatchDateView.tsx b/src/components/wired/views/conditions/WiredConditionMatchDateView.tsx
new file mode 100644
index 0000000..f1b9ccc
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionMatchDateView.tsx
@@ -0,0 +1,195 @@
+import { ChangeEvent, FC, useEffect, useMemo, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredConditionBaseView } from './WiredConditionBaseView';
+
+const MODE_SKIP = 0;
+const MODE_EXACT = 1;
+const MODE_RANGE = 2;
+const MODE_OPTIONS = [ MODE_SKIP, MODE_EXACT, MODE_RANGE ];
+const WEEKDAY_OPTIONS = [ 1, 2, 3, 4, 5, 6, 7 ];
+const MONTH_OPTIONS = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
+
+const createMask = (values: number[]) => values.reduce((mask, value) => (mask | (1 << value)), 0);
+const ALL_WEEKDAYS_MASK = createMask(WEEKDAY_OPTIONS);
+const ALL_MONTHS_MASK = createMask(MONTH_OPTIONS);
+
+const clampValue = (value: number, min: number, max: number) =>
+{
+ if(isNaN(value)) return min;
+
+ return Math.max(min, Math.min(max, Math.floor(value)));
+};
+
+const parseInputValue = (event: ChangeEvent
, min: number, max: number) =>
+{
+ return clampValue(parseInt(event.target.value || min.toString(), 10), min, max);
+};
+
+const toggleMaskValue = (mask: number, value: number, enabled: boolean) =>
+{
+ if(enabled) return (mask | (1 << value));
+
+ return (mask & ~(1 << value));
+};
+
+const InlineNumberInput: FC<{ value: number; min: number; max: number; onChange: (value: number) => void }> = props =>
+{
+ const { value = 0, min = 0, max = 0, onChange = null } = props;
+
+ return (
+ onChange(parseInputValue(event, min, max)) } />
+ );
+};
+
+interface MatchDateSectionProps
+{
+ sectionId: string;
+ titleKey: string;
+ mode: number;
+ fromValue: number;
+ toValue: number;
+ min: number;
+ max: number;
+ onModeChange: (value: number) => void;
+ onFromChange: (value: number) => void;
+ onToChange: (value: number) => void;
+}
+
+const MatchDateSection: FC = props =>
+{
+ const { sectionId = '', titleKey = '', mode = MODE_SKIP, fromValue = 0, toValue = 0, min = 0, max = 0, onModeChange = null, onFromChange = null, onToChange = null } = props;
+
+ return (
+
+ );
+};
+
+export const WiredConditionMatchDateView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null } = useWired();
+ const currentYear = useMemo(() => new Date().getFullYear(), []);
+ const [ weekdayMask, setWeekdayMask ] = useState(ALL_WEEKDAYS_MASK);
+ const [ dayMode, setDayMode ] = useState(MODE_SKIP);
+ const [ dayFrom, setDayFrom ] = useState(1);
+ const [ dayTo, setDayTo ] = useState(31);
+ const [ monthMask, setMonthMask ] = useState(ALL_MONTHS_MASK);
+ const [ yearMode, setYearMode ] = useState(MODE_SKIP);
+ const [ yearFrom, setYearFrom ] = useState(currentYear);
+ const [ yearTo, setYearTo ] = useState(currentYear);
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ setWeekdayMask((trigger.intData[0] && (trigger.intData[0] > 0)) ? trigger.intData[0] : ALL_WEEKDAYS_MASK);
+ setDayMode(MODE_OPTIONS.includes(trigger.intData[1]) ? trigger.intData[1] : MODE_SKIP);
+ setDayFrom(clampValue(trigger.intData[2] ?? 1, 1, 31));
+ setDayTo(clampValue(trigger.intData[3] ?? 31, 1, 31));
+ setMonthMask((trigger.intData[4] && (trigger.intData[4] > 0)) ? trigger.intData[4] : ALL_MONTHS_MASK);
+ setYearMode(MODE_OPTIONS.includes(trigger.intData[5]) ? trigger.intData[5] : MODE_SKIP);
+ setYearFrom(clampValue(trigger.intData[6] ?? currentYear, 1, 9999));
+ setYearTo(clampValue(trigger.intData[7] ?? currentYear, 1, 9999));
+ }, [ currentYear, trigger ]);
+
+ const save = () =>
+ {
+ setIntParams([
+ weekdayMask || ALL_WEEKDAYS_MASK,
+ dayMode,
+ clampValue(dayFrom, 1, 31),
+ clampValue(dayTo, 1, 31),
+ monthMask || ALL_MONTHS_MASK,
+ yearMode,
+ clampValue(yearFrom, 1, 9999),
+ clampValue(yearTo, 1, 9999)
+ ]);
+ };
+
+ return (
+
+
+
+
{ LocalizeText('wiredfurni.params.time.weekday_selection') }
+
+ { WEEKDAY_OPTIONS.map(value =>
+ {
+ const checked = ((weekdayMask & (1 << value)) !== 0);
+
+ return (
+
+ );
+ }) }
+
+
+
setDayFrom(clampValue(value, 1, 31)) }
+ onModeChange={ setDayMode }
+ onToChange={ value => setDayTo(clampValue(value, 1, 31)) } />
+
+
{ LocalizeText('wiredfurni.params.time.month_selection') }
+
+ { MONTH_OPTIONS.map(value =>
+ {
+ const checked = ((monthMask & (1 << value)) !== 0);
+
+ return (
+
+ );
+ }) }
+
+
+ setYearFrom(clampValue(value, 1, 9999)) }
+ onModeChange={ setYearMode }
+ onToChange={ value => setYearTo(clampValue(value, 1, 9999)) } />
+
+
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx b/src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx
new file mode 100644
index 0000000..426ab99
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx
@@ -0,0 +1,163 @@
+import { ChangeEvent, FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredConditionBaseView } from './WiredConditionBaseView';
+
+const MODE_SKIP = 0;
+const MODE_EXACT = 1;
+const MODE_RANGE = 2;
+const MODE_OPTIONS = [ MODE_SKIP, MODE_EXACT, MODE_RANGE ];
+
+const clampValue = (value: number, min: number, max: number) =>
+{
+ if(isNaN(value)) return min;
+
+ return Math.max(min, Math.min(max, Math.floor(value)));
+};
+
+interface TimeFilterSectionProps
+{
+ sectionId: string;
+ titleKey: string;
+ min: number;
+ max: number;
+ mode: number;
+ fromValue: number;
+ toValue: number;
+ onModeChange: (value: number) => void;
+ onFromChange: (value: number) => void;
+ onToChange: (value: number) => void;
+}
+
+const parseInputValue = (event: ChangeEvent, min: number, max: number) =>
+{
+ return clampValue(parseInt(event.target.value || min.toString(), 10), min, max);
+};
+
+const InlineNumberInput: FC<{ value: number; min: number; max: number; onChange: (value: number) => void }> = props =>
+{
+ const { value = 0, min = 0, max = 0, onChange = null } = props;
+
+ return (
+ onChange(parseInputValue(event, min, max)) } />
+ );
+};
+
+const TimeFilterSection: FC = props =>
+{
+ const { sectionId = '', titleKey = '', min = 0, max = 0, mode = MODE_SKIP, fromValue = 0, toValue = 0, onModeChange = null, onFromChange = null, onToChange = null } = props;
+
+ return (
+
+ );
+};
+
+export const WiredConditionMatchTimeView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null } = useWired();
+ const [ hourMode, setHourMode ] = useState(MODE_SKIP);
+ const [ hourFrom, setHourFrom ] = useState(0);
+ const [ hourTo, setHourTo ] = useState(0);
+ const [ minuteMode, setMinuteMode ] = useState(MODE_SKIP);
+ const [ minuteFrom, setMinuteFrom ] = useState(0);
+ const [ minuteTo, setMinuteTo ] = useState(0);
+ const [ secondMode, setSecondMode ] = useState(MODE_SKIP);
+ const [ secondFrom, setSecondFrom ] = useState(0);
+ const [ secondTo, setSecondTo ] = useState(0);
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ setHourMode(MODE_OPTIONS.includes(trigger.intData[0]) ? trigger.intData[0] : MODE_SKIP);
+ setHourFrom(clampValue(trigger.intData[1] ?? 0, 0, 23));
+ setHourTo(clampValue(trigger.intData[2] ?? 0, 0, 23));
+ setMinuteMode(MODE_OPTIONS.includes(trigger.intData[3]) ? trigger.intData[3] : MODE_SKIP);
+ setMinuteFrom(clampValue(trigger.intData[4] ?? 0, 0, 59));
+ setMinuteTo(clampValue(trigger.intData[5] ?? 0, 0, 59));
+ setSecondMode(MODE_OPTIONS.includes(trigger.intData[6]) ? trigger.intData[6] : MODE_SKIP);
+ setSecondFrom(clampValue(trigger.intData[7] ?? 0, 0, 59));
+ setSecondTo(clampValue(trigger.intData[8] ?? 0, 0, 59));
+ }, [ trigger ]);
+
+ const save = () =>
+ {
+ setIntParams([
+ hourMode,
+ clampValue(hourFrom, 0, 23),
+ clampValue(hourTo, 0, 23),
+ minuteMode,
+ clampValue(minuteFrom, 0, 59),
+ clampValue(minuteTo, 0, 59),
+ secondMode,
+ clampValue(secondFrom, 0, 59),
+ clampValue(secondTo, 0, 59)
+ ]);
+ };
+
+ return (
+
+
+ setHourFrom(clampValue(value, 0, 23)) }
+ onModeChange={ setHourMode }
+ onToChange={ value => setHourTo(clampValue(value, 0, 23)) } />
+ setMinuteFrom(clampValue(value, 0, 59)) }
+ onModeChange={ setMinuteMode }
+ onToChange={ value => setMinuteTo(clampValue(value, 0, 59)) } />
+ setSecondFrom(clampValue(value, 0, 59)) }
+ onModeChange={ setSecondMode }
+ onToChange={ value => setSecondTo(clampValue(value, 0, 59)) } />
+
+
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx b/src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx
new file mode 100644
index 0000000..1df5347
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx
@@ -0,0 +1,102 @@
+import { FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredSourcesSelector } from '../WiredSourcesSelector';
+import { WiredConditionBaseView } from './WiredConditionBaseView';
+
+const TEAM_OPTIONS = [ 0, 1, 2, 3, 4 ];
+const PLACEMENT_OPTIONS = [ 1, 2, 3, 4 ];
+
+export const WiredConditionTeamHasRankView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null } = useWired();
+ const [ team, setTeam ] = useState(1);
+ const [ placement, setPlacement ] = useState(1);
+ const [ userSource, setUserSource ] = useState(0);
+ const [ quantifier, setQuantifier ] = useState(0);
+ const [ showAdvanced, setShowAdvanced ] = useState(false);
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ const nextTeam = (trigger.intData.length > 0) ? trigger.intData[0] : 1;
+ const nextPlacement = (trigger.intData.length > 1) ? trigger.intData[1] : 1;
+ const nextUserSource = (trigger.intData.length > 2) ? trigger.intData[2] : 0;
+ const nextQuantifier = (trigger.intData.length > 3) ? trigger.intData[3] : 0;
+
+ setTeam(TEAM_OPTIONS.includes(nextTeam) ? nextTeam : 1);
+ setPlacement(PLACEMENT_OPTIONS.includes(nextPlacement) ? nextPlacement : 1);
+ setUserSource(nextUserSource);
+ setQuantifier((nextQuantifier === 1) ? 1 : 0);
+ setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0);
+ }, [ trigger ]);
+
+ const save = () =>
+ {
+ setIntParams([
+ team,
+ placement,
+ userSource,
+ quantifier
+ ]);
+ };
+
+ return (
+
+
+ { showAdvanced &&
+ <>
+
+
{ LocalizeText('wiredfurni.params.quantifier_selection') }
+ { [ 0, 1 ].map(value =>
+ {
+ return (
+
+ setQuantifier(value) } />
+ { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }
+
+ );
+ }) }
+
+
+ > }
+
+ }>
+
+
{ LocalizeText('wiredfurni.params.team') }
+ { TEAM_OPTIONS.map(value =>
+ {
+ const labelKey = (value === 0) ? 'wiredfurni.params.team.triggerer' : `wiredfurni.params.team.${ value }`;
+
+ return (
+
+ setTeam(value) } />
+ { LocalizeText(labelKey) }
+
+ );
+ }) }
+
+
+
{ LocalizeText('wiredfurni.params.placement_selection') }
+ { PLACEMENT_OPTIONS.map(value =>
+ {
+ return (
+
+ setPlacement(value) } />
+ { LocalizeText(`wiredfurni.params.placement.${ value }`) }
+
+ );
+ }) }
+
+
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx b/src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx
new file mode 100644
index 0000000..a2317aa
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx
@@ -0,0 +1,158 @@
+import { FC, useEffect, 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 TEAM_OPTIONS = [ 1, 2, 3, 4 ];
+const COMPARISON_OPTIONS = [ 0, 1, 2 ];
+const MIN_SCORE = 0;
+const MAX_SCORE = 999;
+const SCORE_PATTERN = /^\d*$/;
+
+const clampScore = (value: number) =>
+{
+ if(isNaN(value)) return MIN_SCORE;
+
+ return Math.max(MIN_SCORE, Math.min(MAX_SCORE, Math.floor(value)));
+};
+
+export const WiredConditionTeamHasScoreView: FC<{}> = () =>
+{
+ const { trigger = null, setIntParams = null } = useWired();
+ const [ team, setTeam ] = useState(1);
+ const [ comparison, setComparison ] = useState(1);
+ const [ score, setScore ] = useState(0);
+ const [ scoreInput, setScoreInput ] = useState('0');
+ const [ userSource, setUserSource ] = useState(0);
+ const [ quantifier, setQuantifier ] = useState(0);
+ const [ showAdvanced, setShowAdvanced ] = useState(false);
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ const nextTeam = (trigger.intData.length > 0) ? trigger.intData[0] : 1;
+ const nextComparison = (trigger.intData.length > 1) ? trigger.intData[1] : 1;
+ const nextScore = clampScore((trigger.intData.length > 2) ? trigger.intData[2] : 0);
+ const nextUserSource = (trigger.intData.length > 3) ? trigger.intData[3] : 0;
+ const nextQuantifier = (trigger.intData.length > 4) ? trigger.intData[4] : 0;
+
+ setTeam(TEAM_OPTIONS.includes(nextTeam) ? nextTeam : 1);
+ setComparison(COMPARISON_OPTIONS.includes(nextComparison) ? nextComparison : 1);
+ setScore(nextScore);
+ setScoreInput(nextScore.toString());
+ setUserSource(nextUserSource);
+ setQuantifier((nextQuantifier === 1) ? 1 : 0);
+ setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0);
+ }, [ trigger ]);
+
+ const updateScore = (value: number) =>
+ {
+ const nextValue = clampScore(value);
+
+ setScore(nextValue);
+ setScoreInput(nextValue.toString());
+ };
+
+ const updateScoreInput = (value: string) =>
+ {
+ if(!SCORE_PATTERN.test(value)) return;
+
+ setScoreInput(value);
+
+ if(!value.length)
+ {
+ setScore(0);
+ return;
+ }
+
+ updateScore(parseInt(value));
+ };
+
+ const save = () =>
+ {
+ setIntParams([
+ team,
+ comparison,
+ clampScore(score),
+ userSource,
+ quantifier
+ ]);
+ };
+
+ return (
+
+
+ { showAdvanced &&
+ <>
+
+
{ LocalizeText('wiredfurni.params.quantifier_selection') }
+ { [ 0, 1 ].map(value =>
+ {
+ return (
+
+ setQuantifier(value) } />
+ { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }
+
+ );
+ }) }
+
+
+ > }
+
+ }>
+
+
{ LocalizeText('wiredfurni.params.team') }
+ { TEAM_OPTIONS.map(value =>
+ {
+ return (
+
+ setTeam(value) } />
+ { LocalizeText(`wiredfurni.params.team.${ value }`) }
+
+ );
+ }) }
+
+
+
{ LocalizeText('wiredfurni.params.comparison_selection') }
+ { COMPARISON_OPTIONS.map(value =>
+ {
+ return (
+
+ setComparison(value) } />
+ { LocalizeText(`wiredfurni.params.comparison.${ value }`) }
+
+ );
+ }) }
+
+
+ { LocalizeText('wiredfurni.params.setscore2') }
+ setScoreInput(clampScore(score).toString()) }
+ onChange={ event => updateScoreInput(event.target.value) } />
+
+
+ updateScore(event as number) } />
+ { score }
+
+
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx b/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx
new file mode 100644
index 0000000..d66403b
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx
@@ -0,0 +1,130 @@
+import { FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { NitroInput } from '../../../../layout';
+import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
+import { WiredConditionBaseView } from './WiredConditionBaseView';
+
+const ENTITY_HABBO = 1;
+const ENTITY_PET = 2;
+const ENTITY_BOT = 4;
+const AVATAR_MODE_ANY = 0;
+const AVATAR_MODE_CERTAIN = 1;
+const SOURCE_SPECIFIED_USERNAME = 101;
+
+const MATCH_USER_SOURCES: 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 COMPARE_USER_SOURCES: WiredSourceOption[] = [
+ ...MATCH_USER_SOURCES,
+ { value: SOURCE_SPECIFIED_USERNAME, label: 'wiredfurni.params.sources.users.101' }
+];
+
+export const WiredConditionTriggererMatchView: FC<{}> = () =>
+{
+ const [ entityType, setEntityType ] = useState(ENTITY_HABBO);
+ const [ avatarMode, setAvatarMode ] = useState(AVATAR_MODE_ANY);
+ const [ username, setUsername ] = useState('');
+ const [ matchUserSource, setMatchUserSource ] = useState(0);
+ const [ compareUserSource, setCompareUserSource ] = useState(0);
+ const [ quantifier, setQuantifier ] = useState(0);
+ const [ showAdvanced, setShowAdvanced ] = useState(false);
+ const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
+
+ const needsUsername = (avatarMode === AVATAR_MODE_CERTAIN) || (compareUserSource === SOURCE_SPECIFIED_USERNAME);
+
+ const save = () =>
+ {
+ setIntParams([
+ entityType,
+ avatarMode,
+ matchUserSource,
+ compareUserSource,
+ quantifier
+ ]);
+ setStringParam(username);
+ };
+
+ useEffect(() =>
+ {
+ if(!trigger) return;
+
+ setEntityType((trigger.intData.length > 0) ? trigger.intData[0] : ENTITY_HABBO);
+ setAvatarMode((trigger.intData.length > 1) ? trigger.intData[1] : AVATAR_MODE_ANY);
+ setMatchUserSource((trigger.intData.length > 2) ? trigger.intData[2] : 0);
+ setCompareUserSource((trigger.intData.length > 3) ? trigger.intData[3] : 0);
+ setQuantifier((trigger.intData.length > 4) ? trigger.intData[4] : 0);
+ setUsername(trigger.stringData || '');
+ setShowAdvanced((trigger.intData.length > 2) ? (trigger.intData[2] !== 0 || trigger.intData[3] !== 0 || trigger.intData[4] !== 0) : false);
+ }, [ trigger ]);
+
+ return (
+
+
+ { showAdvanced &&
+ <>
+
+
{ LocalizeText('wiredfurni.params.quantifier_selection') }
+ { [ 0, 1 ].map(value =>
+ {
+ return (
+
+ setQuantifier(value) } />
+ { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }
+
+ );
+ }) }
+
+
+
+ > }
+
+ }>
+
+ { [ ENTITY_HABBO, ENTITY_PET, ENTITY_BOT ].map(value =>
+ {
+ return (
+
+ setEntityType(value) } />
+ { LocalizeText(`wiredfurni.params.usertype.${ value }`) }
+
+ );
+ }) }
+
+
+
{ LocalizeText('wiredfurni.params.picktriggerer') }
+
+ setAvatarMode(AVATAR_MODE_ANY) } />
+ { LocalizeText('wiredfurni.params.anyavatar') }
+
+
+ setAvatarMode(AVATAR_MODE_CERTAIN) } />
+ { LocalizeText('wiredfurni.params.certainavatar') }
+
+ { needsUsername &&
+
setUsername(event.target.value) } /> }
+
+
+ );
+};
diff --git a/src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx b/src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx
new file mode 100644
index 0000000..a26c859
--- /dev/null
+++ b/src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx
@@ -0,0 +1,151 @@
+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 { WiredConditionBaseView } from './WiredConditionBaseView';
+
+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' }
+];
+
+const USER_ACTION_SOURCES: 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' }
+];
+
+interface WiredConditionUserPerformsActionViewProps
+{
+ negative?: boolean;
+}
+
+export const WiredConditionUserPerformsActionView: FC = props =>
+{
+ const { negative = false } = props;
+ 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 [ userSource, setUserSource ] = useState(0);
+ const [ quantifier, setQuantifier ] = useState(0);
+ const [ showAdvanced, setShowAdvanced ] = useState(false);
+ const { trigger = null, setIntParams = null } = useWired();
+ const quantifierKeyPrefix = negative ? 'wiredfurni.params.quantifier.users.neg' : 'wiredfurni.params.quantifier.users';
+
+ const save = () => setIntParams([
+ selectedAction,
+ signFilterEnabled ? 1 : 0,
+ signId,
+ danceFilterEnabled ? 1 : 0,
+ danceId,
+ userSource,
+ quantifier
+ ]);
+
+ useEffect(() =>
+ {
+ setSelectedAction((trigger?.intData?.length > 0) ? trigger.intData[0] : ACTION_WAVE);
+ setSignFilterEnabled((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
+ setSignId((trigger?.intData?.length > 2) ? trigger.intData[2] : 0);
+ setDanceFilterEnabled((trigger?.intData?.length > 3) ? (trigger.intData[3] === 1) : false);
+ setDanceId((trigger?.intData?.length > 4) ? trigger.intData[4] : 1);
+ setUserSource((trigger?.intData?.length > 5) ? trigger.intData[5] : 0);
+ setQuantifier((trigger?.intData?.length > 6) ? trigger.intData[6] : 0);
+ setShowAdvanced((trigger?.intData?.length > 5) ? (trigger.intData[5] !== 0 || trigger.intData[6] !== 0) : false);
+ }, [ trigger ]);
+
+ return (
+
+
+ { showAdvanced &&
+ <>
+
+
{ LocalizeText('wiredfurni.params.quantifier_selection') }
+ { [ 0, 1 ].map(value =>
+ {
+ return (
+
+ setQuantifier(value) } />
+ { LocalizeText(`${ quantifierKeyPrefix }.${ value }`) }
+
+ );
+ }) }
+
+
+ > }
+
+ }>
+
+ Action
+
+
+ { (selectedAction === ACTION_SIGN) &&
+
+
+ setSignFilterEnabled(event.target.checked) } />
+ { LocalizeText('wiredfurni.params.sign_filter') }
+
+ { signFilterEnabled &&
+
}
+
}
+ { (selectedAction === ACTION_DANCE) &&
+
+
+ setDanceFilterEnabled(event.target.checked) } />
+ { LocalizeText('wiredfurni.params.dance_filter') }
+
+ { danceFilterEnabled &&
+
}
+
}
+
+ );
+};
diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarLeaveRoomView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarLeaveRoomView.tsx
new file mode 100644
index 0000000..36a773e
--- /dev/null
+++ b/src/components/wired/views/triggers/WiredTriggerAvatarLeaveRoomView.tsx
@@ -0,0 +1,39 @@
+import { FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { NitroInput } from '../../../../layout';
+import { WiredTriggerBaseView } from './WiredTriggerBaseView';
+
+export const WiredTriggerAvatarLeaveRoomView: FC<{}> = props =>
+{
+ const [ username, setUsername ] = useState('');
+ const [ avatarMode, setAvatarMode ] = useState(0);
+ const { trigger = null, setStringParam = null } = useWired();
+
+ const save = () => setStringParam((avatarMode === 1) ? username : '');
+
+ useEffect(() =>
+ {
+ setUsername(trigger.stringData);
+ setAvatarMode(trigger.stringData ? 1 : 0);
+ }, [ trigger ]);
+
+ return (
+
+
+
{ LocalizeText('wiredfurni.params.picktriggerer') }
+
+ setAvatarMode(0) } />
+ { LocalizeText('wiredfurni.params.anyavatar') }
+
+
+ setAvatarMode(1) } />
+ { LocalizeText('wiredfurni.params.certainavatar') }
+
+ { (avatarMode === 1) &&
+
setUsername(event.target.value) } /> }
+
+
+ );
+};
diff --git a/src/components/wired/views/triggers/WiredTriggerClickFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerClickFurniView.tsx
new file mode 100644
index 0000000..db8fbea
--- /dev/null
+++ b/src/components/wired/views/triggers/WiredTriggerClickFurniView.tsx
@@ -0,0 +1,8 @@
+import { FC } from 'react';
+import { WiredFurniType } from '../../../../api';
+import { WiredTriggerBaseView } from './WiredTriggerBaseView';
+
+export const WiredTriggerClickFurniView: FC<{}> = () =>
+{
+ return
;
+};
diff --git a/src/components/wired/views/triggers/WiredTriggerClickTileView.tsx b/src/components/wired/views/triggers/WiredTriggerClickTileView.tsx
new file mode 100644
index 0000000..5a1fdc7
--- /dev/null
+++ b/src/components/wired/views/triggers/WiredTriggerClickTileView.tsx
@@ -0,0 +1,20 @@
+import { FC, useEffect } from 'react';
+import { WiredFurniType } from '../../../../api';
+import { useWired } from '../../../../hooks';
+import { WiredTriggerBaseView } from './WiredTriggerBaseView';
+
+const CLICK_TILE_INTERACTION_TYPES = [ 'room_invisible_click_tile' ];
+
+export const WiredTriggerClickTileView: FC<{}> = () =>
+{
+ const { setAllowedInteractionTypes } = useWired();
+
+ useEffect(() =>
+ {
+ setAllowedInteractionTypes(CLICK_TILE_INTERACTION_TYPES);
+
+ return () => setAllowedInteractionTypes(null);
+ }, [ setAllowedInteractionTypes ]);
+
+ return
;
+};
diff --git a/src/components/wired/views/triggers/WiredTriggerClickUserView.tsx b/src/components/wired/views/triggers/WiredTriggerClickUserView.tsx
new file mode 100644
index 0000000..f3f2222
--- /dev/null
+++ b/src/components/wired/views/triggers/WiredTriggerClickUserView.tsx
@@ -0,0 +1,8 @@
+import { FC } from 'react';
+import { WiredFurniType } from '../../../../api';
+import { WiredTriggerBaseView } from './WiredTriggerBaseView';
+
+export const WiredTriggerClickUserView: FC<{}> = () =>
+{
+ return
;
+};
diff --git a/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyShortView.tsx b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyShortView.tsx
new file mode 100644
index 0000000..642b5cd
--- /dev/null
+++ b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyShortView.tsx
@@ -0,0 +1,32 @@
+import { FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Slider, Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredTriggerBaseView } from './WiredTriggerBaseView';
+
+export const WiredTriggeExecutePeriodicallyShortView: FC<{}> = () =>
+{
+ const [ time, setTime ] = useState(10);
+ const { trigger = null, setIntParams = null } = useWired();
+
+ const save = () => setIntParams([ time ]);
+
+ useEffect(() =>
+ {
+ setTime((trigger.intData.length > 0) ? trigger.intData[0] : 10);
+ }, [ trigger ]);
+
+ return (
+
+
+ { LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ ((time * 50) / 1000).toFixed(2) ]) }
+ { `${ time * 50 } ms` }
+ setTime(event) } />
+
+
+ );
+};
diff --git a/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx
index 229464f..3b152fb 100644
--- a/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx
+++ b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx
@@ -1,14 +1,20 @@
import { WiredTriggerLayout } from '../../../../api';
import { WiredTriggerAvatarEnterRoomView } from './WiredTriggerAvatarEnterRoomView';
+import { WiredTriggerAvatarLeaveRoomView } from './WiredTriggerAvatarLeaveRoomView';
import { WiredTriggerAvatarSaysSomethingView } from './WiredTriggerAvatarSaysSomethingView';
import { WiredTriggerAvatarWalksOffFurniView } from './WiredTriggerAvatarWalksOffFurniView';
import { WiredTriggerAvatarWalksOnFurniView } from './WiredTriggerAvatarWalksOnFurni';
import { WiredTriggerBotReachedAvatarView } from './WiredTriggerBotReachedAvatarView';
import { WiredTriggerBotReachedStuffView } from './WiredTriggerBotReachedStuffView';
+import { WiredTriggerClickFurniView } from './WiredTriggerClickFurniView';
+import { WiredTriggerClickTileView } from './WiredTriggerClickTileView';
+import { WiredTriggerClickUserView } from './WiredTriggerClickUserView';
import { WiredTriggerCollisionView } from './WiredTriggerCollisionView';
+import { WiredTriggerUserPerformsActionView } from './WiredTriggerUserPerformsActionView';
import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView';
import { WiredTriggeExecutePeriodicallyLongView } from './WiredTriggerExecutePeriodicallyLongView';
import { WiredTriggeExecutePeriodicallyView } from './WiredTriggerExecutePeriodicallyView';
+import { WiredTriggeExecutePeriodicallyShortView } from './WiredTriggerExecutePeriodicallyShortView';
import { WiredTriggerGameEndsView } from './WiredTriggerGameEndsView';
import { WiredTriggerGameStartsView } from './WiredTriggerGameStartsView';
import { WiredTriggeScoreAchievedView } from './WiredTriggerScoreAchievedView';
@@ -21,6 +27,8 @@ export const WiredTriggerLayoutView = (code: number) =>
{
case WiredTriggerLayout.AVATAR_ENTERS_ROOM:
return
;
+ case WiredTriggerLayout.AVATAR_LEAVES_ROOM:
+ return
;
case WiredTriggerLayout.AVATAR_SAYS_SOMETHING:
return
;
case WiredTriggerLayout.AVATAR_WALKS_OFF_FURNI:
@@ -31,12 +39,22 @@ export const WiredTriggerLayoutView = (code: number) =>
return
;
case WiredTriggerLayout.BOT_REACHED_STUFF:
return
;
+ case WiredTriggerLayout.CLICK_FURNI:
+ return
;
+ case WiredTriggerLayout.CLICK_TILE:
+ return
;
+ case WiredTriggerLayout.CLICK_USER:
+ return
;
+ case WiredTriggerLayout.USER_PERFORMS_ACTION:
+ return
;
case WiredTriggerLayout.COLLISION:
return
;
case WiredTriggerLayout.EXECUTE_ONCE:
return
;
case WiredTriggerLayout.EXECUTE_PERIODICALLY:
return
;
+ case WiredTriggerLayout.EXECUTE_PERIODICALLY_SHORT:
+ return
;
case WiredTriggerLayout.EXECUTE_PERIODICALLY_LONG:
return
;
case WiredTriggerLayout.GAME_ENDS:
diff --git a/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx
index 4748481..01cb8a4 100644
--- a/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx
+++ b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx
@@ -1,8 +1,34 @@
-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 WiredTriggerToggleFurniView: FC<{}> = props =>
+export const WiredTriggerToggleFurniView: FC<{}> = () =>
{
- return
;
+ const [ triggerMode, setTriggerMode ] = useState(0);
+ const { trigger = null, setIntParams = null } = useWired();
+
+ const save = () => setIntParams([ triggerMode ]);
+
+ useEffect(() =>
+ {
+ setTriggerMode((trigger?.intData?.length > 0) ? trigger.intData[0] : 0);
+ }, [ trigger ]);
+
+ return (
+
+
+
{ LocalizeText('wiredfurni.params.condition.state') }
+
+ setTriggerMode(1) } />
+ { LocalizeText('wiredfurni.params.state_trigger.1') }
+
+
+ setTriggerMode(0) } />
+ { LocalizeText('wiredfurni.params.state_trigger.0') }
+
+
+
+ );
};
diff --git a/src/components/wired/views/triggers/WiredTriggerUserPerformsActionView.tsx b/src/components/wired/views/triggers/WiredTriggerUserPerformsActionView.tsx
new file mode 100644
index 0000000..5e68651
--- /dev/null
+++ b/src/components/wired/views/triggers/WiredTriggerUserPerformsActionView.tsx
@@ -0,0 +1,103 @@
+import { FC, useEffect, useState } from 'react';
+import { LocalizeText, WiredFurniType } from '../../../../api';
+import { Text } from '../../../../common';
+import { useWired } from '../../../../hooks';
+import { WiredTriggerBaseView } from './WiredTriggerBaseView';
+
+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 WiredTriggerUserPerformsActionView: 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 { trigger = null, setIntParams = null } = useWired();
+
+ const save = () => setIntParams([
+ selectedAction,
+ signFilterEnabled ? 1 : 0,
+ signId,
+ danceFilterEnabled ? 1 : 0,
+ danceId
+ ]);
+
+ useEffect(() =>
+ {
+ setSelectedAction((trigger?.intData?.length > 0) ? trigger.intData[0] : ACTION_WAVE);
+ setSignFilterEnabled((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
+ setSignId((trigger?.intData?.length > 2) ? trigger.intData[2] : 0);
+ setDanceFilterEnabled((trigger?.intData?.length > 3) ? (trigger.intData[3] === 1) : false);
+ setDanceId((trigger?.intData?.length > 4) ? trigger.intData[4] : 1);
+ }, [ trigger ]);
+
+ return (
+
+
+ Action
+
+
+ { (selectedAction === ACTION_SIGN) &&
+
+
+ setSignFilterEnabled(event.target.checked) } />
+ { LocalizeText('wiredfurni.params.sign_filter') }
+
+ { signFilterEnabled &&
+
}
+
}
+ { (selectedAction === ACTION_DANCE) &&
+
+
+ setDanceFilterEnabled(event.target.checked) } />
+ { LocalizeText('wiredfurni.params.dance_filter') }
+
+ { danceFilterEnabled &&
+
}
+
}
+
+ );
+};
diff --git a/src/css/common/Buttons.css b/src/css/common/Buttons.css
index 21d7f1f..106a3cc 100644
--- a/src/css/common/Buttons.css
+++ b/src/css/common/Buttons.css
@@ -24,8 +24,8 @@ input[type=number] {
.btn-primary {
color: #fff;
- background-color: var(--ui-btn-primary-bg, #3c6d82);
- border: 2px solid var(--ui-btn-primary-border, #1a617f);
+ background-color: #3c6d82;
+ border: 2px solid #1a617f;
padding: 0.25rem 0.5rem;
font-size: .7875rem;
border-radius: 0.5rem;
@@ -33,7 +33,7 @@ input[type=number] {
}
.btn-primary:hover {
- border: 2px solid var(--ui-btn-primary-border, #1a617f);
+ border: 2px solid #1a617f;
box-shadow: none!important;
}
@@ -81,16 +81,16 @@ input[type=number] {
.btn-dark {
color: #fff;
- background-color: var(--ui-dark-bg, #212131);
- border: 2px solid var(--ui-dark-border, #1c1c2a);
+ background-color: #212131;
+ border: 2px solid #1c1c2a;
box-shadow: none!important;
border-radius: 8px;
padding: 4px 11px 4px 11px;
}
.btn-dark:hover{
- background-color: var(--ui-dark-bg, #212131);
- border: 2px solid var(--ui-dark-border, #1c1c2a);
+ background-color: #212131;
+ border: 2px solid #1c1c2a;
box-shadow: none!important;
border-radius: 8px;
padding: 4px 11px 4px 11px;
diff --git a/src/css/index.css b/src/css/index.css
index 36d3b21..806e0e5 100644
--- a/src/css/index.css
+++ b/src/css/index.css
@@ -13,11 +13,16 @@ body {
width: 100%;
height: 100%;
overflow: hidden;
+ background-color: #000;
-webkit-user-select: none;
user-select: none;
scrollbar-width: thin;
}
+.image-rendering-pixelated {
+ image-rendering: pixelated;
+}
+
*,
*:focus,
*:hover {
diff --git a/src/css/notification/NotificationCenterView.css b/src/css/notification/NotificationCenterView.css
index af7ba41..45e3e4b 100644
--- a/src/css/notification/NotificationCenterView.css
+++ b/src/css/notification/NotificationCenterView.css
@@ -59,8 +59,8 @@
.alertView_nitro-coolui-logo {
width: 150px;
- height: 78px;
+ height: 73px;
position: relative;
- background-image: url("@/assets/images/notifications/coolui.png");
+ background-image: url("@/assets/images/notifications/nitro_v3.png");
background-repeat: no-repeat;
}
\ No newline at end of file
diff --git a/src/css/purse/PurseView.css b/src/css/purse/PurseView.css
index 69c1732..7134551 100644
--- a/src/css/purse/PurseView.css
+++ b/src/css/purse/PurseView.css
@@ -22,7 +22,7 @@
pointer-events: all;
}
.borderhccontent{
- background-color: var(--ui-dark-bg, #212131);
+ background-color: #212131;
border-radius: 0.5rem!important;
border: 2px solid #383853;
height: calc(100% - 3px);
@@ -46,7 +46,7 @@
}
.nitro-purse-seasonal-currency {
- background-color: var(--ui-dark-bg, #212131);
+ background-color: #212131;
background: linear-gradient(to right, #5f5f8d, transparent);
height: 30px;
margin-bottom: 4px;
diff --git a/src/css/room/InfoStand.css b/src/css/room/InfoStand.css
index 7e1a050..e44b062 100644
--- a/src/css/room/InfoStand.css
+++ b/src/css/room/InfoStand.css
@@ -27,7 +27,7 @@
width: clamp(160px, 20vw, 190px); /* Responsive width */
z-index: 30;
pointer-events: auto;
- background: var(--ui-dark-bg, #212131);
+ background: #212131;
box-shadow: inset 0 5px rgba(38, 38, 57, 0.6), inset 0 -4px rgba(25, 25, 37, 0.6);
border-radius: 0.5rem;
padding: 10px;
diff --git a/src/css/room/RoomWidgets.css b/src/css/room/RoomWidgets.css
index b4d6ee2..093fa67 100644
--- a/src/css/room/RoomWidgets.css
+++ b/src/css/room/RoomWidgets.css
@@ -4,7 +4,7 @@
left: 15px;
.nitro-room-tools {
- background: var(--ui-dark-bg, #212131);
+ background: #212131;
box-shadow: inset 0px 5px lighten(rgba(#000, .6), 2.5), inset 0 -4px darken(rgba(#000, .6), 4);
border-top-right-radius: .25rem;
border-bottom-right-radius: .25rem;
@@ -54,7 +54,7 @@
}
.nitro-room-history {
- background: var(--ui-dark-bg, #212131);
+ background: #212131;
box-shadow: inset 0px 5px lighten(rgba(#000, .6), 2.5), inset 0 -4px darken(rgba(#000, .6), 4);
transition: all .2s ease;
width: 150px;
@@ -63,7 +63,7 @@
}
.nitro-room-tools-info {
- background: var(--ui-dark-bg, #212131);
+ background: #212131;
box-shadow: inset 0px 5px lighten(rgba(#000, .6), 2.5), inset 0 -4px darken(rgba(#000, .6), 4);
transition: all .2s ease;
max-width: 250px;
diff --git a/src/hooks/navigator/useNavigator.ts b/src/hooks/navigator/useNavigator.ts
index 90df9fc..416c0af 100644
--- a/src/hooks/navigator/useNavigator.ts
+++ b/src/hooks/navigator/useNavigator.ts
@@ -1,4 +1,4 @@
-import { CanCreateRoomEventEvent, CantConnectMessageParser, CreateLinkEvent, DoorbellMessageEvent, FavouriteChangedEvent, FavouritesEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetSessionDataManager, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser, NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSavedSearch, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchResultSet, NavigatorTopLevelContext, RoomDataParser, RoomDoorbellAcceptedEvent, RoomEnterErrorEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent, SecurityLevel, UserEventCatsEvent, UserFlatCatsEvent, UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer';
+import { CanCreateRoomEventEvent, CantConnectMessageParser, CreateLinkEvent, DoorbellMessageEvent, FavouriteChangedEvent, FavouritesEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetRoomSessionManager, GetSessionDataManager, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser, NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSavedSearch, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchResultSet, NavigatorTopLevelContext, RoomDataParser, RoomDoorbellAcceptedEvent, RoomEnterErrorEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent, SecurityLevel, UserEventCatsEvent, UserFlatCatsEvent, UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer';
import { useState } from 'react';
import { useBetween } from 'use-between';
import { CreateRoomSession, DoorStateType, GetConfigurationValue, INavigatorData, LocalizeText, NotificationAlertType, SendMessageComposer, TryVisitRoom, VisitDesktop } from '../../api';
@@ -397,6 +397,8 @@ const useNavigatorState = () =>
return;
}
+ if(GetRoomSessionManager().viewerSession) return;
+
let forwardType = -1;
let forwardId = -1;
diff --git a/src/hooks/notification/useNotification.ts b/src/hooks/notification/useNotification.ts
index 4cd3f68..41e85d1 100644
--- a/src/hooks/notification/useNotification.ts
+++ b/src/hooks/notification/useNotification.ts
@@ -1,4 +1,4 @@
-import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d } from '@nitrots/nitro-renderer';
+import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d, WiredRewardResultMessageEvent } from '@nitrots/nitro-renderer';
import { useCallback, useState } from 'react';
import { useBetween } from 'use-between';
import { GetConfigurationValue, LocalizeBadgeName, LocalizeText, NotificationAlertItem, NotificationAlertType, NotificationBubbleItem, NotificationBubbleType, NotificationConfirmItem, PlaySound, ProductImageUtility, TradingNotificationType } from '../../api';
@@ -397,6 +397,28 @@ const useNotificationState = () =>
simpleAlert(LocalizeText(parser.alertMessage), NotificationAlertType.DEFAULT, null, null, LocalizeText(parser.titleMessage ? parser.titleMessage : 'notifications.broadcast.title'));
});
+ useMessageEvent
(WiredRewardResultMessageEvent, event =>
+ {
+ const parser = event.getParser();
+
+ switch(parser.reason)
+ {
+ case WiredRewardResultMessageEvent.PRODUCT_DONATED_CODE:
+ case WiredRewardResultMessageEvent.BADGE_DONATED_CODE:
+ simpleAlert(LocalizeText('wiredfurni.rewardsuccess.body'), NotificationAlertType.DEFAULT, null, null, LocalizeText('wiredfurni.rewardsuccess.title'));
+ return;
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 8:
+ simpleAlert(LocalizeText(`wiredfurni.rewardfailed.reason.${ parser.reason }`), NotificationAlertType.DEFAULT, null, null, LocalizeText('wiredfurni.rewardfailed.title'));
+ return;
+ }
+ });
+
const onRoomEnterEvent = useCallback(() =>
{
if(modDisclaimerShown) return;
diff --git a/src/hooks/rooms/useRoom.ts b/src/hooks/rooms/useRoom.ts
index 743ce87..157ab03 100644
--- a/src/hooks/rooms/useRoom.ts
+++ b/src/hooks/rooms/useRoom.ts
@@ -1,7 +1,7 @@
import { ColorConverter, GetRenderer, GetRoomEngine, GetStage, IRoomSession, NitroAdjustmentFilter, NitroSprite, NitroTexture, RoomBackgroundColorEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomGeometry, RoomId, RoomObjectCategory, RoomObjectHSLColorEnabledEvent, RoomObjectOperationType, RoomSessionEvent, RoomVariableEnum, Vector3d } from '@nitrots/nitro-renderer';
import { useEffect, useState } from 'react';
import { useBetween } from 'use-between';
-import { CanManipulateFurniture, DispatchUiEvent, GetRoomSession, InitializeRoomInstanceRenderingCanvas, IsFurnitureSelectionDisabled, ProcessRoomObjectOperation, RoomWidgetUpdateBackgroundColorPreviewEvent, RoomWidgetUpdateRoomObjectEvent, SetActiveRoomId, StartRoomSession } from '../../api';
+import { CanManipulateFurniture, DispatchUiEvent, GetRoomSession, IsFurnitureSelectionDisabled, ProcessRoomObjectOperation, RoomWidgetUpdateBackgroundColorPreviewEvent, RoomWidgetUpdateRoomObjectEvent, SetActiveRoomId, StartRoomSession } from '../../api';
import { useNitroEvent, useUiEvent } from '../events';
const useRoomState = () =>
@@ -253,15 +253,20 @@ const useRoomState = () =>
const resize = (event: UIEvent) =>
{
- const width = Math.floor(window.innerWidth);
- const height = Math.floor(window.innerHeight);
+ const newWidth = Math.floor(window.innerWidth);
+ const newHeight = Math.floor(window.innerHeight);
- renderer.resize(width, height, window.devicePixelRatio);
+ const offsetX = canvas.screenOffsetX - (newWidth - canvas.width) / 2;
+ const offsetY = canvas.screenOffsetY - (newHeight - canvas.height) / 2;
- background.width = width;
- background.height = height;
+ renderer.resize(newWidth, newHeight, window.devicePixelRatio);
- InitializeRoomInstanceRenderingCanvas(width, height, 1);
+ background.width = newWidth;
+ background.height = newHeight;
+
+ canvas.initialize(newWidth, newHeight);
+ canvas.screenOffsetX = ~~offsetX;
+ canvas.screenOffsetY = ~~offsetY;
};
window.addEventListener('resize', resize);
diff --git a/src/hooks/rooms/widgets/useChatWidget.ts b/src/hooks/rooms/widgets/useChatWidget.ts
index d430a38..648a102 100644
--- a/src/hooks/rooms/widgets/useChatWidget.ts
+++ b/src/hooks/rooms/widgets/useChatWidget.ts
@@ -123,9 +123,10 @@ const useChatWidgetState = () =>
text = LocalizeText('widget.chatbubble.handitem', ['username', 'handitem'], [username, LocalizeText(('handitem' + event.extraParam))]);
break;
case RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING: {
- const hours = ((event.extraParam > 0) ? Math.floor((event.extraParam / 3600)) : 0).toString();
- const minutes = ((event.extraParam > 0) ? Math.floor((event.extraParam % 3600) / 60) : 0).toString();
- const seconds = (event.extraParam % 60).toString();
+ const remainingSeconds = Math.max(0, event.extraParam);
+ const hours = Math.floor(remainingSeconds / 3600).toString();
+ const minutes = Math.floor((remainingSeconds % 3600) / 60).toString();
+ const seconds = (remainingSeconds % 60).toString();
text = LocalizeText('widget.chatbubble.mutetime', ['hours', 'minutes', 'seconds'], [hours, minutes, seconds]);
break;
diff --git a/src/hooks/wired/useWired.ts b/src/hooks/wired/useWired.ts
index b0c3f32..19e6ed8 100644
--- a/src/hooks/wired/useWired.ts
+++ b/src/hooks/wired/useWired.ts
@@ -235,6 +235,7 @@ const useWiredState = () =>
{
const parser = event.getParser();
+ WiredSelectionVisualizer.clearAllSelectionShaders();
setTrigger(null);
});
@@ -275,6 +276,7 @@ const useWiredState = () =>
return () =>
{
+ WiredSelectionVisualizer.clearAllSelectionShaders();
setIntParams([]);
setStringParam('');
setActionDelay(0);