Split WiredCreatorToolsView: extract types/constants/helpers into 3 sibling files

The single-file WiredCreatorToolsView.tsx was 4493 lines, which is one
of the main reasons the React Compiler reports
"Compilation Skipped: Existing memoization could not be preserved" on
this module. Split is conservative — only the pure leading sections
move out, the component itself is untouched (state, effects, JSX all
stay in place).

New files (sibling to the view):
- WiredCreatorTools.types.ts (~233 lines): every interface and type
  alias declared at the top of the original file.
- WiredCreatorTools.constants.ts (~225 lines): TABS, MONITOR_LOG_ORDER,
  poll constants, MONITOR_ERROR_INFO, INSPECTION_ELEMENTS,
  VARIABLES_ELEMENTS, EDITABLE_*, VARIABLE_DEFINITIONS,
  WIRED_FREEZE_EFFECT_IDS, TEAM_COLOR_NAMES, WEEKDAY/MONTH/DIRECTION
  names. The createVariableDefinition factory is kept as a local helper
  in this file (only used to build VARIABLE_DEFINITIONS).
- WiredCreatorTools.helpers.ts (~147 lines): createEmptyMonitorSnapshot,
  getHotelTimeFormatter (with its module-private cache map),
  getHotelDateTimeParts, formatMonitorLatestOccurrence,
  formatMonitorHistoryOccurrence, formatVariableTimestamp,
  formatMonitorSource, normalizeMonitorReason. All pure (or
  cache-stable), no closure on component state.

WiredCreatorToolsView.tsx changes:
- 4493 -> 3901 lines (-592, ~13% reduction).
- The four inspection-icon asset imports (furni/global/user/context)
  move to the constants file alongside the only consumers
  (INSPECTION_ELEMENTS / VARIABLES_ELEMENTS).
- AvatarInfoFurni was only referenced by the extracted
  InspectionFurniSelection interface and is removed from the main
  file's api import.
- New import block at the top pulls back the symbols actually used by
  the component body.

Verification:
- yarn eslint on the three new files: 0 errors / 0 warnings.
- yarn eslint on WiredCreatorToolsView.tsx: 26 errors before split,
  26 errors after split (identical pre-existing set; nothing new
  introduced).
- yarn tsc --noEmit on the four files: clean (only the project-wide
  pre-existing TS2307 about @nitrots/nitro-renderer not being
  installed locally remains, same as before).

This unblocks future per-tab splits (Monitor / Inspection / Variables
JSX panels are still inline in the view and represent the next ~1600
lines that could move out, but require introducing a shared state
context first since the current setState chain is intertwined).

https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
simoleo89
2026-05-11 16:31:52 +00:00
parent 39eb2c6b84
commit 5d8717dedb
4 changed files with 609 additions and 597 deletions
@@ -0,0 +1,225 @@
import contextInspectionIcon from '../../assets/images/wiredtools/context.png';
import furniInspectionIcon from '../../assets/images/wiredtools/furni.png';
import globalInspectionIcon from '../../assets/images/wiredtools/global.png';
import userInspectionIcon from '../../assets/images/wiredtools/user.png';
import { InspectionElementButton, VariableDefinition, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
export 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' }
];
export const MONITOR_LOG_ORDER: string[] = [
'EXECUTION_CAP',
'DELAYED_EVENTS_CAP',
'EXECUTOR_OVERLOAD',
'MARKED_AS_HEAVY',
'KILLED',
'RECURSION_TIMEOUT'
];
export const WIRED_MONITOR_ACTION_FETCH = 0;
export const WIRED_MONITOR_ACTION_CLEAR_LOGS = 1;
export const WIRED_MONITOR_POLL_MS = 50;
export const WIRED_VARIABLES_POLL_MS = 50;
export const WIRED_INSPECTION_REFRESH_MS = 50;
export const WIRED_CLOCK_REFRESH_MS = 50;
export const MONITOR_ERROR_INFO: Record<string, { description: string[]; severity: string; title: string; }> = {
EXECUTION_CAP: {
title: 'EXECUTION_CAP',
severity: 'ERROR',
description: [
'This error occurs when the maximum Wired usage limit is about to be exceeded by a Wired execution.',
'When this happens, the current execution is cancelled so the room never goes over the configured usage budget.',
'If this happens too often, it usually means the setup is too complex for the amount of triggers firing in a short time.'
]
},
DELAYED_EVENTS_CAP: {
title: 'DELAYED_EVENTS_CAP',
severity: 'ERROR',
description: [
'Delayed Wired events happen when effects are scheduled to run later.',
'There is a limit to how many delayed events can be pending at the same time. Once the limit is reached, new delayed executions are refused.',
'If this appears often, the setup is likely relying too heavily on delayed effects and should be simplified.'
]
},
EXECUTOR_OVERLOAD: {
title: 'EXECUTOR_OVERLOAD',
severity: 'ERROR',
description: [
'This error occurs when the Wired engine is receiving a lot of instructions and the room cannot keep up with the execution time.',
'This can be a sign of server pressure or of a setup that is too expensive to evaluate repeatedly.',
'If the room is also marked as heavy, it is a good sign that the setup should be reduced or optimized.'
]
},
MARKED_AS_HEAVY: {
title: 'MARKED_AS_HEAVY',
severity: 'WARNING',
description: [
'The room is being considered heavy because its Wired usage stays high across multiple monitor windows.',
'This is not a fatal error by itself, but it means the room is consuming a significant portion of the execution budget.',
'If the room is not intentionally complex, it is worth reviewing the setup before it starts triggering harder limits.'
]
},
KILLED: {
title: 'KILLED',
severity: 'ERROR',
description: [
'This happens when the room is temporarily halted by the protection layer because the Wired flow looks abusive or unstable.',
'While the room is killed, Wired execution is paused for a cooldown period.',
'This is usually caused by loops, event spam, or repeated limit violations.'
]
},
RECURSION_TIMEOUT: {
title: 'RECURSION_TIMEOUT',
severity: 'ERROR',
description: [
'Recursive Wired events happen when signals keep re-triggering other stacks in the same room.',
'When the recursion depth limit is reached, execution is stopped to prevent runaway loops.',
'In most cases this means two or more stacks are indirectly calling each other too many times.'
]
}
};
export const INSPECTION_ELEMENTS: InspectionElementButton[] = [
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
{ key: 'user', label: 'User', icon: userInspectionIcon },
{ key: 'global', label: 'Global', icon: globalInspectionIcon }
];
export const VARIABLES_ELEMENTS: VariablesElementButton[] = [
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
{ key: 'user', label: 'User', icon: userInspectionIcon },
{ key: 'global', label: 'Global', icon: globalInspectionIcon },
{ key: 'context', label: 'Context', icon: contextInspectionIcon }
];
export const EDITABLE_FURNI_VARIABLES: string[] = [ '@position_x', '@position_y', '@rotation', '@altitude', '@state', '@wallitem_offset' ];
export const EDITABLE_USER_VARIABLES: string[] = [ '@position_x', '@position_y', '@direction' ];
const createVariableDefinition = (key: string, target: 'Furni' | 'User' | 'Global' | 'Context', availability: string = 'Always', canWriteTo = false): VariableDefinition =>
({
key,
target,
type: 'Internal',
hasValue: true,
availability,
canWriteTo,
canCreateDelete: false,
canIntercept: false,
hasCreationTime: false,
hasUpdateTime: false,
isTextConnected: false,
isAlwaysAvailable: (availability === 'Always')
});
export const VARIABLE_DEFINITIONS: Record<VariablesElementType, VariableDefinition[]> = {
furni: [
createVariableDefinition('~teleport.target_id', 'Furni', 'Conditional'),
createVariableDefinition('@id', 'Furni'),
createVariableDefinition('@class_id', 'Furni'),
createVariableDefinition('@height', 'Furni'),
createVariableDefinition('@state', 'Furni', 'Always', true),
createVariableDefinition('@position_x', 'Furni', 'Always', true),
createVariableDefinition('@position_y', 'Furni', 'Always', true),
createVariableDefinition('@rotation', 'Furni', 'Always', true),
createVariableDefinition('@altitude', 'Furni', 'Always', true),
createVariableDefinition('@is_invisible', 'Furni', 'Conditional'),
createVariableDefinition('@wallitem_offset', 'Furni', 'Conditional', true),
createVariableDefinition('@type', 'Furni'),
createVariableDefinition('@can_sit_on', 'Furni', 'Conditional'),
createVariableDefinition('@can_lay_on', 'Furni', 'Conditional'),
createVariableDefinition('@can_stand_on', 'Furni', 'Conditional'),
createVariableDefinition('@is_stackable', 'Furni', 'Conditional'),
createVariableDefinition('@dimensions.x', 'Furni'),
createVariableDefinition('@dimensions.y', 'Furni'),
createVariableDefinition('@owner_id', 'Furni')
],
user: [
createVariableDefinition('@index', 'User'),
createVariableDefinition('@type', 'User'),
createVariableDefinition('@gender', 'User'),
createVariableDefinition('@level', 'User'),
createVariableDefinition('@achievement_score', 'User'),
createVariableDefinition('@is_hc', 'User', 'Conditional'),
createVariableDefinition('@has_rights', 'User', 'Conditional'),
createVariableDefinition('@is_owner', 'User', 'Conditional'),
createVariableDefinition('@is_group_admin', 'User', 'Conditional'),
createVariableDefinition('@is_muted', 'User', 'Conditional'),
createVariableDefinition('@is_trading', 'User', 'Conditional'),
createVariableDefinition('@is_frozen', 'User', 'Conditional'),
createVariableDefinition('@effect_id', 'User', 'Conditional'),
createVariableDefinition('@team_score', 'User', 'Conditional'),
createVariableDefinition('@team_color', 'User', 'Conditional'),
createVariableDefinition('@team_type', 'User', 'Conditional'),
createVariableDefinition('@sign', 'User', 'Conditional'),
createVariableDefinition('@dance', 'User', 'Conditional'),
createVariableDefinition('@is_idle', 'User', 'Conditional'),
createVariableDefinition('@handitem_id', 'User', 'Conditional'),
createVariableDefinition('@position_x', 'User', 'Always', true),
createVariableDefinition('@position_y', 'User', 'Always', true),
createVariableDefinition('@direction', 'User', 'Always', true),
createVariableDefinition('@altitude', 'User'),
createVariableDefinition('@favourite_group_id', 'User', 'Conditional'),
createVariableDefinition('@room_entry.method', 'User', 'Conditional'),
createVariableDefinition('@room_entry.teleport_id', 'User', 'Conditional'),
createVariableDefinition('@user_id', 'User', 'Conditional'),
createVariableDefinition('@bot_id', 'User', 'Conditional'),
createVariableDefinition('@pet_id', 'User', 'Conditional'),
createVariableDefinition('@pet_owner_id', 'User', 'Conditional')
],
global: [
createVariableDefinition('@furni_count', 'Global'),
createVariableDefinition('@user_count', 'Global'),
createVariableDefinition('@wired_timer', 'Global'),
createVariableDefinition('@team_red_score', 'Global'),
createVariableDefinition('@team_green_score', 'Global'),
createVariableDefinition('@team_blue_score', 'Global'),
createVariableDefinition('@team_yellow_score', 'Global'),
createVariableDefinition('@team_red_size', 'Global'),
createVariableDefinition('@team_green_size', 'Global'),
createVariableDefinition('@team_blue_size', 'Global'),
createVariableDefinition('@team_yellow_size', 'Global'),
createVariableDefinition('@room_id', 'Global'),
createVariableDefinition('@group_id', 'Global'),
createVariableDefinition('@timezone_server', 'Global'),
createVariableDefinition('@timezone_client', 'Global'),
createVariableDefinition('@current_time', 'Global'),
createVariableDefinition('@current_time.millisecond_of_second', 'Global'),
createVariableDefinition('@current_time.seconds_of_minute', 'Global'),
createVariableDefinition('@current_time.minute_of_hour', 'Global'),
createVariableDefinition('@current_time.hour_of_day', 'Global'),
createVariableDefinition('@current_time.day_of_week', 'Global'),
createVariableDefinition('@current_time.day_of_month', 'Global'),
createVariableDefinition('@current_time.day_of_year', 'Global'),
createVariableDefinition('@current_time.week_of_year', 'Global'),
createVariableDefinition('@current_time.month_of_year', 'Global'),
createVariableDefinition('@current_time.year', 'Global')
],
context: [
createVariableDefinition('@selector_furni_count', 'Context', 'Conditional'),
createVariableDefinition('@selector_user_count', 'Context', 'Conditional'),
createVariableDefinition('@signal_furni_count', 'Context', 'Conditional'),
createVariableDefinition('@signal_user_count', 'Context', 'Conditional'),
createVariableDefinition('@antenna_id', 'Context', 'Conditional'),
createVariableDefinition('@chat_type', 'Context', 'Conditional'),
createVariableDefinition('@chat_style', 'Context', 'Conditional')
]
};
export const WIRED_FREEZE_EFFECT_IDS: Set<number> = new Set([ 218, 12, 11, 53, 163 ]);
export const TEAM_COLOR_NAMES: Record<number, string> = {
1: 'red',
2: 'green',
3: 'blue',
4: 'yellow'
};
export const WEEKDAY_NAMES: string[] = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ];
export const MONTH_NAMES: string[] = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
export const DIRECTION_NAMES: string[] = [ 'North', 'North-East', 'East', 'South-East', 'South', 'South-West', 'West', 'North-West' ];
@@ -0,0 +1,147 @@
import { HotelDateTimeParts, MonitorSnapshot } from './WiredCreatorTools.types';
const HOTEL_TIME_FORMATTERS: Map<string, Intl.DateTimeFormat> = new Map();
export const createEmptyMonitorSnapshot = (): MonitorSnapshot =>
({
usageCurrentWindow: 0,
usageLimitPerWindow: 0,
isHeavy: false,
delayedEventsPending: 0,
delayedEventsLimit: 0,
averageExecutionMs: 0,
peakExecutionMs: 0,
recursionDepthCurrent: 0,
recursionDepthLimit: 0,
killedRemainingSeconds: 0,
usageWindowMs: 0,
overloadAverageThresholdMs: 0,
overloadPeakThresholdMs: 0,
heavyUsageThresholdPercent: 0,
heavyConsecutiveWindowsThreshold: 0,
overloadConsecutiveWindowsThreshold: 0,
heavyDelayedThresholdPercent: 0,
logs: [],
history: []
});
export const getHotelTimeFormatter = (timeZone: string): Intl.DateTimeFormat =>
{
const formatterTimeZone = (timeZone || 'UTC');
const existingFormatter = HOTEL_TIME_FORMATTERS.get(formatterTimeZone);
if(existingFormatter) return existingFormatter;
let formatter: Intl.DateTimeFormat = null;
try
{
formatter = new Intl.DateTimeFormat('en-GB', {
timeZone: formatterTimeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
});
}
catch
{
formatter = new Intl.DateTimeFormat('en-GB', {
timeZone: 'UTC',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
});
}
HOTEL_TIME_FORMATTERS.set(formatterTimeZone, formatter);
return formatter;
};
export const getHotelDateTimeParts = (epochMs: number, timeZone: string): HotelDateTimeParts =>
{
const normalizedEpochMs = Number.isFinite(epochMs) ? epochMs : Date.now();
const date = new Date(normalizedEpochMs);
const formatter = getHotelTimeFormatter(timeZone);
const formattedParts = formatter.formatToParts(date);
const partsMap = new Map<string, string>();
for(const part of formattedParts)
{
if(part.type === 'literal') continue;
partsMap.set(part.type, part.value);
}
return {
year: Number(partsMap.get('year') ?? date.getUTCFullYear()),
month: Number(partsMap.get('month') ?? (date.getUTCMonth() + 1)),
day: Number(partsMap.get('day') ?? date.getUTCDate()),
hour: Number(partsMap.get('hour') ?? date.getUTCHours()),
minute: Number(partsMap.get('minute') ?? date.getUTCMinutes()),
second: Number(partsMap.get('second') ?? date.getUTCSeconds()),
millisecond: (((normalizedEpochMs % 1000) + 1000) % 1000)
};
};
export const formatMonitorLatestOccurrence = (latestOccurrenceSeconds: number, nowMs: number): string =>
{
if(latestOccurrenceSeconds <= 0) return '/';
const diffMs = Math.max(0, (nowMs - (latestOccurrenceSeconds * 1000)));
const diffSeconds = Math.floor(diffMs / 1000);
if(diffSeconds < 5) return 'Just now';
if(diffSeconds < 60) return `${ diffSeconds }s ago`;
const diffMinutes = Math.floor(diffSeconds / 60);
if(diffMinutes < 60) return `${ diffMinutes }m ago`;
const diffHours = Math.floor(diffMinutes / 60);
if(diffHours < 24) return `${ diffHours }h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${ diffDays }d ago`;
};
export const formatMonitorHistoryOccurrence = (occurredAtSeconds: number): string =>
{
if(occurredAtSeconds <= 0) return '/';
return new Date(occurredAtSeconds * 1000).toLocaleString('en-GB');
};
export const formatVariableTimestamp = (timestamp: number): string =>
{
if(!timestamp || (timestamp <= 0)) return '/';
return new Date(timestamp * 1000).toLocaleString('en-GB');
};
export const formatMonitorSource = (sourceLabel: string, sourceId: number): string =>
{
const normalizedLabel = (sourceLabel || '').trim();
if(!normalizedLabel && !(sourceId > 0)) return 'Room monitor';
if(sourceId > 0) return `${ normalizedLabel || 'wired' } (#${ sourceId })`;
return normalizedLabel;
};
export const normalizeMonitorReason = (reason: string): string =>
{
const normalizedReason = (reason || '').trim();
return normalizedReason || 'No detailed reason was recorded for this entry.';
};
@@ -0,0 +1,233 @@
import { AvatarInfoFurni } from '../../api';
export type WiredToolsTab = 'monitor' | 'variables' | 'inspection' | 'chests' | 'settings';
export type InspectionElementType = 'furni' | 'user' | 'global';
export type VariablesElementType = InspectionElementType | 'context';
export interface InspectionElementButton
{
key: InspectionElementType;
label: string;
icon: string;
}
export interface VariablesElementButton
{
key: VariablesElementType;
label: string;
icon: string;
disabled?: boolean;
}
export interface InspectionFurniSelection
{
objectId: number;
category: number;
info: AvatarInfoFurni;
}
export interface InspectionFurniLiveState
{
positionX: number;
positionY: number;
altitude: number;
rotation: number;
state: number;
}
export interface InspectionUserSelection
{
kind: 'user' | 'bot' | 'rentable_bot' | 'pet';
roomIndex: number;
name: string;
figure: string;
gender: string;
userId: number;
level: number;
achievementScore: number;
isHC: boolean;
hasRights: boolean;
isOwner: boolean;
favouriteGroupId: number;
roomEntryMethod: string;
roomEntryTeleportId: number;
posture?: string;
}
export interface InspectionUserLiveState
{
positionX: number;
positionY: number;
altitude: number;
direction: number;
}
export interface MonitorStat
{
label: string;
value: string;
}
export interface MonitorLog
{
type: string;
category: string;
amount: string;
latest: string;
latestReason: string;
latestSourceId: number;
latestSourceLabel: string;
}
export interface MonitorSnapshot
{
usageCurrentWindow: number;
usageLimitPerWindow: number;
isHeavy: boolean;
delayedEventsPending: number;
delayedEventsLimit: number;
averageExecutionMs: number;
peakExecutionMs: number;
recursionDepthCurrent: number;
recursionDepthLimit: number;
killedRemainingSeconds: number;
usageWindowMs: number;
overloadAverageThresholdMs: number;
overloadPeakThresholdMs: number;
heavyUsageThresholdPercent: number;
heavyConsecutiveWindowsThreshold: number;
overloadConsecutiveWindowsThreshold: number;
heavyDelayedThresholdPercent: number;
logs: Array<{
amount: number;
latestOccurrenceSeconds: number;
latestReason: string;
latestSourceId: number;
latestSourceLabel: string;
severity: string;
type: string;
}>;
history: Array<{
occurredAtSeconds: number;
reason: string;
sourceId: number;
sourceLabel: string;
severity: string;
type: string;
}>;
}
export interface MonitorLogDetails
{
amount?: string;
latest?: string;
occurredAt?: string;
reason: string;
severity: string;
sourceId: number;
sourceLabel: string;
type: string;
}
export interface InspectionVariable
{
key: string;
value: string;
editable?: boolean;
valueClassName?: string;
}
export interface VariableDefinition
{
key: string;
itemId?: number;
target: 'Furni' | 'User' | 'Global' | 'Context';
type: string;
hasValue: boolean;
isReadOnly?: boolean;
availability: string;
canWriteTo: boolean;
canCreateDelete: boolean;
canIntercept: boolean;
hasCreationTime: boolean;
hasUpdateTime: boolean;
isTextConnected: boolean;
isAlwaysAvailable?: boolean;
}
export interface VariableTextValue
{
value: string;
text: string;
}
export interface VariableManageEntry
{
categoryLabel: string;
createdAt: number;
entityId: number;
entityName: string;
manageLabel: string;
updatedAt: number;
value: number | null;
}
export interface VariableHighlightTarget
{
category: number;
hasValue: boolean;
objectId: number;
value: number | null;
}
export interface VariableHighlightOverlay extends VariableHighlightTarget
{
key: string;
x: number;
y: number;
}
export interface ManagedHolderVariableEntry
{
availability: string;
createdAt: number;
hasValue: boolean;
name: string;
isReadOnly?: boolean;
updatedAt: number;
value: number | null;
variableItemId: number;
}
export interface InspectionUserTeamData
{
colorId: number;
typeId: number;
score: number;
}
export interface TeamEffectData
{
colorId: number;
typeId: number;
}
export interface ParsedWallLocation
{
width: number;
height: number;
localX: number;
localY: number;
direction: string;
}
export interface HotelDateTimeParts
{
year: number;
month: number;
day: number;
hour: number;
minute: number;
second: number;
millisecond: number;
}
@@ -1,609 +1,16 @@
import { AddLinkEventTracker, AvatarExpressionEnum, FigureUpdateEvent, FurnitureFloorUpdateEvent, FurnitureMultiStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, GetStage, GetTicker, ILinkEventTracker, RemoveLinkEventTracker, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitInfoEvent, RoomUnitStatusEvent, UpdateFurniturePositionComposer, Vector3d, WiredUserInspectMoveComposer } from '@nitrots/nitro-renderer';
import { WiredMonitorDataEvent, WiredMonitorRequestComposer } from '@nitrots/nitro-renderer';
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import furniInspectionIcon from '../../assets/images/wiredtools/furni.png';
import globalInspectionIcon from '../../assets/images/wiredtools/global.png';
import userInspectionIcon from '../../assets/images/wiredtools/user.png';
import contextInspectionIcon from '../../assets/images/wiredtools/context.png';
import wiredGlobalPlaceholderImage from '../../assets/images/wiredtools/wired_global_placeholder.png';
import wiredMonitorImage from '../../assets/images/wiredtools/wired_monitor.png';
import { AvatarInfoFurni, AvatarInfoUtilities, GetRoomObjectBounds, GetRoomObjectScreenLocation, LocalizeText, NotificationAlertType, SendMessageComposer, WiredSelectionVisualizer } from '../../api';
import { AvatarInfoUtilities, GetRoomObjectBounds, GetRoomObjectScreenLocation, LocalizeText, NotificationAlertType, SendMessageComposer, WiredSelectionVisualizer } from '../../api';
import { Button, DraggableWindowPosition, LayoutAvatarImageView, LayoutPetImageView, LayoutRoomObjectImageView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common';
import { useInventoryTrade, useMessageEvent, useNotification, useObjectSelectedEvent, useRoom, useWiredTools } from '../../hooks';
import { DIRECTION_NAMES, EDITABLE_FURNI_VARIABLES, EDITABLE_USER_VARIABLES, INSPECTION_ELEMENTS, MONITOR_ERROR_INFO, MONITOR_LOG_ORDER, MONTH_NAMES, TABS, TEAM_COLOR_NAMES, VARIABLES_ELEMENTS, VARIABLE_DEFINITIONS, WEEKDAY_NAMES, WIRED_CLOCK_REFRESH_MS, WIRED_FREEZE_EFFECT_IDS, WIRED_INSPECTION_REFRESH_MS, WIRED_MONITOR_ACTION_CLEAR_LOGS, WIRED_MONITOR_ACTION_FETCH, WIRED_MONITOR_POLL_MS, WIRED_VARIABLES_POLL_MS } from './WiredCreatorTools.constants';
import { createEmptyMonitorSnapshot, formatMonitorHistoryOccurrence, formatMonitorLatestOccurrence, formatMonitorSource, formatVariableTimestamp, getHotelDateTimeParts, getHotelTimeFormatter, normalizeMonitorReason } from './WiredCreatorTools.helpers';
import { HotelDateTimeParts, InspectionElementButton, InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, InspectionUserTeamData, InspectionVariable, ManagedHolderVariableEntry, MonitorLog, MonitorLogDetails, MonitorSnapshot, MonitorStat, ParsedWallLocation, TeamEffectData, VariableDefinition, VariableHighlightOverlay, VariableHighlightTarget, VariableManageEntry, VariableTextValue, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
import { WiredToolsSettingsTabView } from './WiredToolsSettingsTabView';
type WiredToolsTab = 'monitor' | 'variables' | 'inspection' | 'chests' | 'settings';
type InspectionElementType = 'furni' | 'user' | 'global';
type VariablesElementType = InspectionElementType | 'context';
interface InspectionElementButton
{
key: InspectionElementType;
label: string;
icon: string;
}
interface VariablesElementButton
{
key: VariablesElementType;
label: string;
icon: string;
disabled?: boolean;
}
interface InspectionFurniSelection
{
objectId: number;
category: number;
info: AvatarInfoFurni;
}
interface InspectionFurniLiveState
{
positionX: number;
positionY: number;
altitude: number;
rotation: number;
state: number;
}
interface InspectionUserSelection
{
kind: 'user' | 'bot' | 'rentable_bot' | 'pet';
roomIndex: number;
name: string;
figure: string;
gender: string;
userId: number;
level: number;
achievementScore: number;
isHC: boolean;
hasRights: boolean;
isOwner: boolean;
favouriteGroupId: number;
roomEntryMethod: string;
roomEntryTeleportId: number;
posture?: string;
}
interface InspectionUserLiveState
{
positionX: number;
positionY: number;
altitude: number;
direction: number;
}
interface MonitorStat
{
label: string;
value: string;
}
interface MonitorLog
{
type: string;
category: string;
amount: string;
latest: string;
latestReason: string;
latestSourceId: number;
latestSourceLabel: string;
}
interface MonitorSnapshot
{
usageCurrentWindow: number;
usageLimitPerWindow: number;
isHeavy: boolean;
delayedEventsPending: number;
delayedEventsLimit: number;
averageExecutionMs: number;
peakExecutionMs: number;
recursionDepthCurrent: number;
recursionDepthLimit: number;
killedRemainingSeconds: number;
usageWindowMs: number;
overloadAverageThresholdMs: number;
overloadPeakThresholdMs: number;
heavyUsageThresholdPercent: number;
heavyConsecutiveWindowsThreshold: number;
overloadConsecutiveWindowsThreshold: number;
heavyDelayedThresholdPercent: number;
logs: Array<{
amount: number;
latestOccurrenceSeconds: number;
latestReason: string;
latestSourceId: number;
latestSourceLabel: string;
severity: string;
type: string;
}>;
history: Array<{
occurredAtSeconds: number;
reason: string;
sourceId: number;
sourceLabel: string;
severity: string;
type: string;
}>;
}
interface MonitorLogDetails
{
amount?: string;
latest?: string;
occurredAt?: string;
reason: string;
severity: string;
sourceId: number;
sourceLabel: string;
type: string;
}
interface InspectionVariable
{
key: string;
value: string;
editable?: boolean;
valueClassName?: string;
}
interface VariableDefinition
{
key: string;
itemId?: number;
target: 'Furni' | 'User' | 'Global' | 'Context';
type: string;
hasValue: boolean;
isReadOnly?: boolean;
availability: string;
canWriteTo: boolean;
canCreateDelete: boolean;
canIntercept: boolean;
hasCreationTime: boolean;
hasUpdateTime: boolean;
isTextConnected: boolean;
isAlwaysAvailable?: boolean;
}
interface VariableTextValue
{
value: string;
text: string;
}
interface VariableManageEntry
{
categoryLabel: string;
createdAt: number;
entityId: number;
entityName: string;
manageLabel: string;
updatedAt: number;
value: number | null;
}
interface VariableHighlightTarget
{
category: number;
hasValue: boolean;
objectId: number;
value: number | null;
}
interface VariableHighlightOverlay extends VariableHighlightTarget
{
key: string;
x: number;
y: number;
}
interface ManagedHolderVariableEntry
{
availability: string;
createdAt: number;
hasValue: boolean;
name: string;
isReadOnly?: boolean;
updatedAt: number;
value: number | null;
variableItemId: number;
}
interface InspectionUserTeamData
{
colorId: number;
typeId: number;
score: number;
}
interface TeamEffectData
{
colorId: number;
typeId: number;
}
interface ParsedWallLocation
{
width: number;
height: number;
localX: number;
localY: number;
direction: string;
}
interface HotelDateTimeParts
{
year: number;
month: number;
day: number;
hour: number;
minute: number;
second: number;
millisecond: number;
}
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_LOG_ORDER: string[] = [
'EXECUTION_CAP',
'DELAYED_EVENTS_CAP',
'EXECUTOR_OVERLOAD',
'MARKED_AS_HEAVY',
'KILLED',
'RECURSION_TIMEOUT'
];
const WIRED_MONITOR_ACTION_FETCH = 0;
const WIRED_MONITOR_ACTION_CLEAR_LOGS = 1;
const WIRED_MONITOR_POLL_MS = 50;
const WIRED_VARIABLES_POLL_MS = 50;
const WIRED_INSPECTION_REFRESH_MS = 50;
const WIRED_CLOCK_REFRESH_MS = 50;
const MONITOR_ERROR_INFO: Record<string, { description: string[]; severity: string; title: string; }> = {
EXECUTION_CAP: {
title: 'EXECUTION_CAP',
severity: 'ERROR',
description: [
'This error occurs when the maximum Wired usage limit is about to be exceeded by a Wired execution.',
'When this happens, the current execution is cancelled so the room never goes over the configured usage budget.',
'If this happens too often, it usually means the setup is too complex for the amount of triggers firing in a short time.'
]
},
DELAYED_EVENTS_CAP: {
title: 'DELAYED_EVENTS_CAP',
severity: 'ERROR',
description: [
'Delayed Wired events happen when effects are scheduled to run later.',
'There is a limit to how many delayed events can be pending at the same time. Once the limit is reached, new delayed executions are refused.',
'If this appears often, the setup is likely relying too heavily on delayed effects and should be simplified.'
]
},
EXECUTOR_OVERLOAD: {
title: 'EXECUTOR_OVERLOAD',
severity: 'ERROR',
description: [
'This error occurs when the Wired engine is receiving a lot of instructions and the room cannot keep up with the execution time.',
'This can be a sign of server pressure or of a setup that is too expensive to evaluate repeatedly.',
'If the room is also marked as heavy, it is a good sign that the setup should be reduced or optimized.'
]
},
MARKED_AS_HEAVY: {
title: 'MARKED_AS_HEAVY',
severity: 'WARNING',
description: [
'The room is being considered heavy because its Wired usage stays high across multiple monitor windows.',
'This is not a fatal error by itself, but it means the room is consuming a significant portion of the execution budget.',
'If the room is not intentionally complex, it is worth reviewing the setup before it starts triggering harder limits.'
]
},
KILLED: {
title: 'KILLED',
severity: 'ERROR',
description: [
'This happens when the room is temporarily halted by the protection layer because the Wired flow looks abusive or unstable.',
'While the room is killed, Wired execution is paused for a cooldown period.',
'This is usually caused by loops, event spam, or repeated limit violations.'
]
},
RECURSION_TIMEOUT: {
title: 'RECURSION_TIMEOUT',
severity: 'ERROR',
description: [
'Recursive Wired events happen when signals keep re-triggering other stacks in the same room.',
'When the recursion depth limit is reached, execution is stopped to prevent runaway loops.',
'In most cases this means two or more stacks are indirectly calling each other too many times.'
]
}
};
const INSPECTION_ELEMENTS: InspectionElementButton[] = [
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
{ key: 'user', label: 'User', icon: userInspectionIcon },
{ key: 'global', label: 'Global', icon: globalInspectionIcon }
];
const VARIABLES_ELEMENTS: VariablesElementButton[] = [
{ key: 'furni', label: 'Furni', icon: furniInspectionIcon },
{ key: 'user', label: 'User', icon: userInspectionIcon },
{ key: 'global', label: 'Global', icon: globalInspectionIcon },
{ key: 'context', label: 'Context', icon: contextInspectionIcon }
];
const EDITABLE_FURNI_VARIABLES: string[] = [ '@position_x', '@position_y', '@rotation', '@altitude', '@state', '@wallitem_offset' ];
const EDITABLE_USER_VARIABLES: string[] = [ '@position_x', '@position_y', '@direction' ];
const createVariableDefinition = (key: string, target: 'Furni' | 'User' | 'Global' | 'Context', availability: string = 'Always', canWriteTo = false): VariableDefinition =>
({
key,
target,
type: 'Internal',
hasValue: true,
availability,
canWriteTo,
canCreateDelete: false,
canIntercept: false,
hasCreationTime: false,
hasUpdateTime: false,
isTextConnected: false,
isAlwaysAvailable: (availability === 'Always')
});
const VARIABLE_DEFINITIONS: Record<VariablesElementType, VariableDefinition[]> = {
furni: [
createVariableDefinition('~teleport.target_id', 'Furni', 'Conditional'),
createVariableDefinition('@id', 'Furni'),
createVariableDefinition('@class_id', 'Furni'),
createVariableDefinition('@height', 'Furni'),
createVariableDefinition('@state', 'Furni', 'Always', true),
createVariableDefinition('@position_x', 'Furni', 'Always', true),
createVariableDefinition('@position_y', 'Furni', 'Always', true),
createVariableDefinition('@rotation', 'Furni', 'Always', true),
createVariableDefinition('@altitude', 'Furni', 'Always', true),
createVariableDefinition('@is_invisible', 'Furni', 'Conditional'),
createVariableDefinition('@wallitem_offset', 'Furni', 'Conditional', true),
createVariableDefinition('@type', 'Furni'),
createVariableDefinition('@can_sit_on', 'Furni', 'Conditional'),
createVariableDefinition('@can_lay_on', 'Furni', 'Conditional'),
createVariableDefinition('@can_stand_on', 'Furni', 'Conditional'),
createVariableDefinition('@is_stackable', 'Furni', 'Conditional'),
createVariableDefinition('@dimensions.x', 'Furni'),
createVariableDefinition('@dimensions.y', 'Furni'),
createVariableDefinition('@owner_id', 'Furni')
],
user: [
createVariableDefinition('@index', 'User'),
createVariableDefinition('@type', 'User'),
createVariableDefinition('@gender', 'User'),
createVariableDefinition('@level', 'User'),
createVariableDefinition('@achievement_score', 'User'),
createVariableDefinition('@is_hc', 'User', 'Conditional'),
createVariableDefinition('@has_rights', 'User', 'Conditional'),
createVariableDefinition('@is_owner', 'User', 'Conditional'),
createVariableDefinition('@is_group_admin', 'User', 'Conditional'),
createVariableDefinition('@is_muted', 'User', 'Conditional'),
createVariableDefinition('@is_trading', 'User', 'Conditional'),
createVariableDefinition('@is_frozen', 'User', 'Conditional'),
createVariableDefinition('@effect_id', 'User', 'Conditional'),
createVariableDefinition('@team_score', 'User', 'Conditional'),
createVariableDefinition('@team_color', 'User', 'Conditional'),
createVariableDefinition('@team_type', 'User', 'Conditional'),
createVariableDefinition('@sign', 'User', 'Conditional'),
createVariableDefinition('@dance', 'User', 'Conditional'),
createVariableDefinition('@is_idle', 'User', 'Conditional'),
createVariableDefinition('@handitem_id', 'User', 'Conditional'),
createVariableDefinition('@position_x', 'User', 'Always', true),
createVariableDefinition('@position_y', 'User', 'Always', true),
createVariableDefinition('@direction', 'User', 'Always', true),
createVariableDefinition('@altitude', 'User'),
createVariableDefinition('@favourite_group_id', 'User', 'Conditional'),
createVariableDefinition('@room_entry.method', 'User', 'Conditional'),
createVariableDefinition('@room_entry.teleport_id', 'User', 'Conditional'),
createVariableDefinition('@user_id', 'User', 'Conditional'),
createVariableDefinition('@bot_id', 'User', 'Conditional'),
createVariableDefinition('@pet_id', 'User', 'Conditional'),
createVariableDefinition('@pet_owner_id', 'User', 'Conditional')
],
global: [
createVariableDefinition('@furni_count', 'Global'),
createVariableDefinition('@user_count', 'Global'),
createVariableDefinition('@wired_timer', 'Global'),
createVariableDefinition('@team_red_score', 'Global'),
createVariableDefinition('@team_green_score', 'Global'),
createVariableDefinition('@team_blue_score', 'Global'),
createVariableDefinition('@team_yellow_score', 'Global'),
createVariableDefinition('@team_red_size', 'Global'),
createVariableDefinition('@team_green_size', 'Global'),
createVariableDefinition('@team_blue_size', 'Global'),
createVariableDefinition('@team_yellow_size', 'Global'),
createVariableDefinition('@room_id', 'Global'),
createVariableDefinition('@group_id', 'Global'),
createVariableDefinition('@timezone_server', 'Global'),
createVariableDefinition('@timezone_client', 'Global'),
createVariableDefinition('@current_time', 'Global'),
createVariableDefinition('@current_time.millisecond_of_second', 'Global'),
createVariableDefinition('@current_time.seconds_of_minute', 'Global'),
createVariableDefinition('@current_time.minute_of_hour', 'Global'),
createVariableDefinition('@current_time.hour_of_day', 'Global'),
createVariableDefinition('@current_time.day_of_week', 'Global'),
createVariableDefinition('@current_time.day_of_month', 'Global'),
createVariableDefinition('@current_time.day_of_year', 'Global'),
createVariableDefinition('@current_time.week_of_year', 'Global'),
createVariableDefinition('@current_time.month_of_year', 'Global'),
createVariableDefinition('@current_time.year', 'Global')
],
context: [
createVariableDefinition('@selector_furni_count', 'Context', 'Conditional'),
createVariableDefinition('@selector_user_count', 'Context', 'Conditional'),
createVariableDefinition('@signal_furni_count', 'Context', 'Conditional'),
createVariableDefinition('@signal_user_count', 'Context', 'Conditional'),
createVariableDefinition('@antenna_id', 'Context', 'Conditional'),
createVariableDefinition('@chat_type', 'Context', 'Conditional'),
createVariableDefinition('@chat_style', 'Context', 'Conditional')
]
};
const WIRED_FREEZE_EFFECT_IDS: Set<number> = new Set([ 218, 12, 11, 53, 163 ]);
const TEAM_COLOR_NAMES: Record<number, string> = {
1: 'red',
2: 'green',
3: 'blue',
4: 'yellow'
};
const WEEKDAY_NAMES: string[] = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ];
const MONTH_NAMES: string[] = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
const DIRECTION_NAMES: string[] = [ 'North', 'North-East', 'East', 'South-East', 'South', 'South-West', 'West', 'North-West' ];
const HOTEL_TIME_FORMATTERS: Map<string, Intl.DateTimeFormat> = new Map();
const createEmptyMonitorSnapshot = (): MonitorSnapshot =>
({
usageCurrentWindow: 0,
usageLimitPerWindow: 0,
isHeavy: false,
delayedEventsPending: 0,
delayedEventsLimit: 0,
averageExecutionMs: 0,
peakExecutionMs: 0,
recursionDepthCurrent: 0,
recursionDepthLimit: 0,
killedRemainingSeconds: 0,
usageWindowMs: 0,
overloadAverageThresholdMs: 0,
overloadPeakThresholdMs: 0,
heavyUsageThresholdPercent: 0,
heavyConsecutiveWindowsThreshold: 0,
overloadConsecutiveWindowsThreshold: 0,
heavyDelayedThresholdPercent: 0,
logs: [],
history: []
});
const getHotelTimeFormatter = (timeZone: string): Intl.DateTimeFormat =>
{
const formatterTimeZone = (timeZone || 'UTC');
const existingFormatter = HOTEL_TIME_FORMATTERS.get(formatterTimeZone);
if(existingFormatter) return existingFormatter;
let formatter: Intl.DateTimeFormat = null;
try
{
formatter = new Intl.DateTimeFormat('en-GB', {
timeZone: formatterTimeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
});
}
catch
{
formatter = new Intl.DateTimeFormat('en-GB', {
timeZone: 'UTC',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
});
}
HOTEL_TIME_FORMATTERS.set(formatterTimeZone, formatter);
return formatter;
};
const getHotelDateTimeParts = (epochMs: number, timeZone: string): HotelDateTimeParts =>
{
const normalizedEpochMs = Number.isFinite(epochMs) ? epochMs : Date.now();
const date = new Date(normalizedEpochMs);
const formatter = getHotelTimeFormatter(timeZone);
const formattedParts = formatter.formatToParts(date);
const partsMap = new Map<string, string>();
for(const part of formattedParts)
{
if(part.type === 'literal') continue;
partsMap.set(part.type, part.value);
}
return {
year: Number(partsMap.get('year') ?? date.getUTCFullYear()),
month: Number(partsMap.get('month') ?? (date.getUTCMonth() + 1)),
day: Number(partsMap.get('day') ?? date.getUTCDate()),
hour: Number(partsMap.get('hour') ?? date.getUTCHours()),
minute: Number(partsMap.get('minute') ?? date.getUTCMinutes()),
second: Number(partsMap.get('second') ?? date.getUTCSeconds()),
millisecond: (((normalizedEpochMs % 1000) + 1000) % 1000)
};
};
const formatMonitorLatestOccurrence = (latestOccurrenceSeconds: number, nowMs: number): string =>
{
if(latestOccurrenceSeconds <= 0) return '/';
const diffMs = Math.max(0, (nowMs - (latestOccurrenceSeconds * 1000)));
const diffSeconds = Math.floor(diffMs / 1000);
if(diffSeconds < 5) return 'Just now';
if(diffSeconds < 60) return `${ diffSeconds }s ago`;
const diffMinutes = Math.floor(diffSeconds / 60);
if(diffMinutes < 60) return `${ diffMinutes }m ago`;
const diffHours = Math.floor(diffMinutes / 60);
if(diffHours < 24) return `${ diffHours }h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${ diffDays }d ago`;
};
const formatMonitorHistoryOccurrence = (occurredAtSeconds: number): string =>
{
if(occurredAtSeconds <= 0) return '/';
return new Date(occurredAtSeconds * 1000).toLocaleString('en-GB');
};
const formatVariableTimestamp = (timestamp: number): string =>
{
if(!timestamp || (timestamp <= 0)) return '/';
return new Date(timestamp * 1000).toLocaleString('en-GB');
};
const formatMonitorSource = (sourceLabel: string, sourceId: number): string =>
{
const normalizedLabel = (sourceLabel || '').trim();
if(!normalizedLabel && !(sourceId > 0)) return 'Room monitor';
if(sourceId > 0) return `${ normalizedLabel || 'wired' } (#${ sourceId })`;
return normalizedLabel;
};
const normalizeMonitorReason = (reason: string): string =>
{
const normalizedReason = (reason || '').trim();
return normalizedReason || 'No detailed reason was recorded for this entry.';
};
export const WiredCreatorToolsView: FC<{}> = () =>
{
const [ isVisible, setIsVisible ] = useState(false);