с использованием Системы;
с использованием System.Collections.Generic;
с использованием System.Linq;
с использованием UnityEngine;
с использованием SimpleJSON;
// Этот скрипт совмещает контроллеры женских бедер и ягодиц с контроллерами мужских пенисов в Virt-A-Mate (VAM).
// Он динамически регулирует положение и вращение женских бедер и ягодиц в зависимости от положения пениса,
// с пользовательским интерфейсом для выбора атомов, настройки параметров и включения выравнивания.
открытый класс DeviningReach: MVRScript
{
    // JSON-хранимые данные для элементов пользовательского интерфейса
    private JSONStorableBool enableAlignment; // Переключите для включения/отключения выравнивания
    private JSONStorableString statusText; // Текстовое поле для отображения сообщений о состоянии
    private JSONStorableFloat attractionStrength; // Ползунок для силы притяжения бедра
    private JSONStorableFloat rotateSpeed; // Ползунок скорости вращения бедра
    private JSONStorableFloat pelvisOffset; // Ползунок для смещения положения таза
    private JSONStorableStringChooser maleAtomChooser; // Всплывающее окно для выбора мужского атома
    private JSONStorableStringChooser femaleAtomChooser; // Всплывающее окно для выбора женского атома
    private JSONStorableString penisBaseID; // ID контроллера базы пениса
    private JSONStorableString penisMidID; // ID контроллера среднего размера пениса
    private JSONStorableString penisTipID; // ID контроллера кончика пениса
    private JSONStorableString hipID; // ID женского контроллера бедра
    private JSONStorableString pelvisID; // ID контроллера женского таза
    private JSONStorableString leftThighID; // ID контроллера левого бедра
    private JSONStorableString rightThighID; // ID контроллера правого бедра
    // Переменные времени выполнения
    private Atom maleAtom; // Выбранный мужской атом
    private Atom femaleAtom; // Выбранный женский атом
    private bool hasValidTargets = false; // True, если найдены все требуемые контроллеры
    private bool isInitialized = false; // True после завершения инициализации
    private float initializationDelay = 1f; // Задержка перед инициализацией контроллеров
    private Dictionary<string, FreeControllerV3> controllers = new Dictionary<string, FreeControllerV3>(); // Сохраняет все контроллеры
    private Dictionary<string, Atom> availableAtoms = new Dictionary<string, Atom>(); // Сохраняет все атомы Person в сцене
    private Vector3 vaginaPosition; // Положение женского бедра (используется как ссылка на влагалище)
    private Vector3 vaginaDirection; // Направление женского бедра вперед
    private float updateLogTimer = 2f; // Таймер для периодического ведения журнала
    private Vector3 lastLeftThighPos; // Конечное положение левого бедра для плавного движения
    private Vector3 lastRightThighPos; // Конечное положение правого бедра для плавного движения
    private Quaternion lastLeftThighRot; // Последний поворот левого бедра для плавного движения
    private Quaternion lastRightThighRot; // Last rotation of right thigh for smooth movement
    // Constants for controller IDs
    private readonly string[] maleControllerIds = { "penisBaseControl", "penisMidControl", "penisTipControl" }; // Male controller IDs
    private readonly string[] femaleControllerIds = { "hipControl", "pelvisControl", "lThighControl", "rThighControl" }; // Female controller IDs
    private const string PluginName = "DeviningReach"; // Plugin name for logging
    private const float MinPositionDelta = 0.001f; // Minimum position change to prevent jittering
    private const float MinRotationDelta = 0.1f; // Minimum rotation change (in degrees) to prevent jittering
    // Initializes the plugin, called when the plugin is loaded or reloaded
    public override void Init()
    {
        try
        {
            SuperController.LogMessage($"{PluginName}: Starting Init");
            // Reset atom choosers to prevent auto-initialization on reload
            if (maleAtomChooser != null) maleAtomChooser.val = "None";
            if (femaleAtomChooser != null) femaleAtomChooser.val = "None";
            maleAtom = null;
            femaleAtom = null;
            isInitialized = false;
            hasValidTargets = false;
            controllers.Clear();
            FindAllPersonAtoms(); // Find all Person atoms in the scene
            CreateUI(); // Create the UI elements
            SuperController.LogMessage($"{PluginName}: Init completed");
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: Init error: {e}");
        }
    }
    // Creates the UI elements for the plugin (toggles, sliders, popups, buttons)
    private void CreateUI()
    {
        try
        {
            // Create toggle to enable/disable alignment
            enableAlignment = new JSONStorableBool("Enable Alignment", true);
            RegisterBool(enableAlignment);
            CreateToggle(enableAlignment);
            // Create status text field
            statusText = new JSONStorableString("Status", "Select one male and one female atom");
            RegisterString(statusText);
            CreateTextField(statusText).height = 100f;
            // Populate atom names for choosers
            List<string> atomNames = new List<string> { "None" };
            atomNames.AddRange(availableAtoms.Keys);
            // Create male atom chooser
            maleAtomChooser = new JSONStorableStringChooser("Male Atom", atomNames, "None", "Male Atom",
                new JSONStorableStringChooser.SetStringCallback(UpdateAtomSelection));
            RegisterStringChooser(maleAtomChooser);
            CreatePopup(maleAtomChooser);
            // Create female atom chooser
            femaleAtomChooser = new JSONStorableStringChooser("Female Atom", atomNames, "None", "Female Atom",
                new JSONStorableStringChooser.SetStringCallback(UpdateAtomSelection));
            RegisterStringChooser(femaleAtomChooser);
            CreatePopup(femaleAtomChooser);
            // Create attraction strength slider (controls thigh movement intensity)
            attractionStrength = new JSONStorableFloat("Attraction Strength", 0.5f, 0.1f, 1.0f);
            RegisterFloat(attractionStrength);
            CreateSlider(attractionStrength);
            // Create rotation speed slider (controls hip rotation speed)
            rotationSpeed = new JSONStorableFloat("Rotation Speed", 2.0f, 0.1f, 10f);
            RegisterFloat(rotationSpeed);
            CreateSlider(rotationSpeed);
            // Create pelvis offset slider (controls pelvis position offset from hip)
            pelvisOffset = new JSONStorableFloat("Pelvis Offset", 0.1f, 0.05f, 0.2f);
            RegisterFloat(pelvisOffset);
            CreateSlider(pelvisOffset);
            // Initialize controller ID storables
            penisBaseID = new JSONStorableString("Penis Base ID", "penisBaseControl");
            RegisterString(penisBaseID);
            penisMidID = new JSONStorableString("Penis Mid ID", "penisMidControl");
            RegisterString(penisMidID);
            penisTipID = new JSONStorableString("Penis Tip ID", "penisTipControl");
            RegisterString(penisTipID);
            hipID = new JSONStorableString("Hip ID", "hipControl");
            RegisterString(hipID);
            pelvisID = new JSONStorableString("Pelvis ID", "pelvisControl");
            RegisterString(pelvisID);
            leftThighID = new JSONStorableString("Left Thigh ID", "lThighControl");
            RegisterString(leftThighID);
            rightThighID = new JSONStorableString("Right Thigh ID", "rThighControl");
            RegisterString(rightThighID);
            // Create button to enable all female control points
            JSONStorableAction enableAllPoints = new JSONStorableAction("Enable All Points", EnableAllPoints);
            RegisterAction(enableAllPoints);
            var enableButton = CreateButton("Enable All Points");
            enableButton.button.onClick.AddListener(() => { EnableAllPoints(); });
            SuperController.LogMessage($"{PluginName}: CreateUI - Enable All Points button registered");
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: CreateUI error: {e}");
        }
    }
    // Updates atom selection when male or female atom is chosen in UI
    private void UpdateAtomSelection(string val)
    {
        try
        {
            SuperController.LogMessage($"{PluginName}: UpdateAtomSelection called with value: {val}");
            // Set male and female atoms based on chooser values
            maleAtom = maleAtomChooser.val != "None" && availableAtoms.ContainsKey(maleAtomChooser.val) ? availableAtoms[maleAtomChooser.val] : null;
            femaleAtom = femaleAtomChooser.val != "None" && availableAtoms.ContainsKey(femaleAtomChooser.val) ? availableAtoms[femaleAtomChooser.val] : null;
            // Prevent selecting the same atom for both male and female
            if (maleAtom != null && femaleAtom != null && maleAtom == femaleAtom)
            {
                SuperController.LogError($"{PluginName}: Cannot select the same atom for both male and female!");
                statusText.val = "Error: Cannot select the same atom for both male and female!";
                femaleAtom = null;
                femaleAtomChooser.val = "None";
            }
            // Reset initialization to trigger controller setup
            isInitialized = false;
            initializationDelay = 1f;
            SuperController.LogMessage($"{PluginName}: Atom selection updated: Male = {maleAtom?.uid ?? "None"}, Female = {femaleAtom?.uid ?? "None"}");
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: UpdateAtomSelection error: {e}");
        }
    }
    // Finds all Person atoms in the scene
    private void FindAllPersonAtoms()
    {
        try
        {
            SuperController.LogMessage($"{PluginName}: Finding all Person atoms");
            availableAtoms.Clear();
            foreach (Atom atom in SuperController.singleton.GetAtoms())
            {
                if (atom.type == "Person")
                {
                    availableAtoms[atom.uid] = atom;
                    SuperController.LogMessage($"{PluginName}: Found Person atom: {atom.uid}");
                }
            }
            if (availableAtoms.Count == 0)
            {
                SuperController.LogError($"{PluginName}: No Person atoms found in scene!");
                statusText.val = "Error: Person atoms not found!";
            }
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: FindAllPersonAtoms error: {e}");
        }
    }
    // Initializes controllers for male and female atoms
    private void InitializeControllers()
    {
        try
        {
            SuperController.LogMessage($"{PluginName}: Initializing controllers");
            controllers.Clear();
            hasValidTargets = false;
            List<string> missingControllers = new List<string>();
            // Load male controllers without changing their states
            if (maleAtom != null)
            {
                foreach (string controlId in maleControllerIds)
                {
                    FreeControllerV3 controller = maleAtom.GetStorableByID(controlId) as FreeControllerV3;
                    if (controller != null)
                    {
                        controllers[controlId] = controller;
                        // Log current states without modifying them
                        SuperController.LogMessage($"{PluginName}: Found {controlId} for {maleAtom.uid}, Current PositionState: {controller.currentPositionState}, Current RotationState: {controller.currentRotationState}, no state changes applied");
                    }
                    else
                    {
                        SuperController.LogMessage($"{PluginName}: Control {controlId} not found for {maleAtom.uid}");
                        missingControllers.Add(controlId);
                    }
                }
            }
            // Load and set states for female controllers
            if (femaleAtom != null)
            {
                foreach (string controlId in femaleControllerIds)
                {
                    FreeControllerV3 controller = femaleAtom.GetStorableByID(controlId) as FreeControllerV3;
                    if (controller != null)
                    {
                        controllers[controlId] = controller;
                        if (controlId.ToLower().Contains("hip"))
                        {
                            controller.currentPositionState = FreeControllerV3.PositionState.On;
                            controller.currentRotationState = FreeControllerV3.RotationState.On;
                            SuperController.LogMessage($"{PluginName}: Initialized {controlId} for {femaleAtom.uid}, Position: On, Rotation: On");
                        }
                        else if (controlId.ToLower().Contains("pelvis"))
                        {
                            controller.currentPositionState = FreeControllerV3.PositionState.ParentLink;
                            controller.currentRotationState = FreeControllerV3.RotationState.ParentLink;
                            SuperController.LogMessage($"{PluginName}: Initialized {controlId} for {femaleAtom.uid}, Position: ParentLink, Rotation: ParentLink");
                        }
                        else
                        {
                            controller.currentPositionState = FreeControllerV3.PositionState.Comply;
                            controller.currentRotationState = FreeControllerV3.RotationState.Comply;
                            SuperController.LogMessage($"{PluginName}: Initialized {controlId} for {femaleAtom.uid}, Position: Comply, Rotation: Comply");
                        }
                    }
                    else
                    {
                        SuperController.LogMessage($"{PluginName}: Control {controlId} not found for {femaleAtom.uid}");
                        missingControllers.Add(controlId);
                    }
                }
            }
            // Check if all required controllers are present
            hasValidTargets = controllers.ContainsKey(penisBaseID.val) &&
                             controllers.ContainsKey(penisTipID.val) &&
                             controllers.ContainsKey(hipID.val) &&
                             controllers.ContainsKey(leftThighID.val) &&
                             controllers.ContainsKey(rightThighID.val);
            if (hasValidTargets)
            {
                SuperController.LogMessage($"{PluginName}: InitializeControllers succeeded");
                if (missingControllers.Count > 0)
                {
                    statusText.val = $"Warning: Controllers not found: {string.Join(", ", missingControllers.ToArray())}";
                }
                else
                {
                    statusText.val = "All female points successfully initialized";
                }
                // Initialize thigh positions and rotations
                if (controllers.ContainsKey(leftThighID.val) && controllers[leftThighID.val].control != null)
                {
                    lastLeftThighPos = controllers[leftThighID.val].control.position;
                    lastLeftThighRot = controllers[leftThighID.val].control.rotation;
                }
                if (controllers.ContainsKey(rightThighID.val) && controllers[rightThighID.val].control != null)
                {
                    lastRightThighPos = controllers[rightThighID.val].control.position;
                    lastRightThighRot = controllers[rightThighID.val].control.rotation;
                }
                UpdatePelvisAlignment(); // Align pelvis initially
            }
            else
            {
                statusText.val = $"Error: Required controllers not found: {string.Join(", ", missingControllers.ToArray())}";
                SuperController.LogError($"{PluginName}: InitializeControllers failed - minimum required controllers not found");
            }
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: InitializeControllers error: {e}");
            statusText.val = "Error initializing controllers";
        }
    }
    // Main update loop, runs every frame
    private void Update()
    {
        try
        {
            updateLogTimer -= Time.deltaTime;
            // Initialize controllers after delay, only if atoms are selected
            if (!isInitialized && initializationDelay > 0f)
            {
                initializationDelay -= Time.deltaTime;
                if (initializationDelay <= 0f && maleAtom != null && femaleAtom != null)
                {
                    InitializeControllers();
                    isInitialized = true;
                    SuperController.LogMessage($"{PluginName}: Delayed initialization completed");
                }
                return;
            }
            // Skip update if alignment is disabled or targets are invalid
            if (!enableAlignment.val || !hasValidTargets)
            {
                if (updateLogTimer <= 0f)
                {
                    SuperController.LogMessage($"{PluginName}: Update skipped - alignment disabled or invalid targets");
                    updateLogTimer = 2f;
                }
                return;
            }
            // Perform alignment calculations
            CalculateVaginaPoints();
            AdaptiveHipsAlignment();
            UpdatePelvisAlignment();
            UpdateThighMovement();
            UpdateStatusText();
            // Reset log timer
            if (updateLogTimer <= 0f)
            {
                updateLogTimer = 2f;
            }
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: Update error: {e}");
        }
    }
    // Calculates the position and direction of the vagina based on hip controller
    private void CalculateVaginaPoints()
    {
        if (controllers.ContainsKey(hipID.val))
        {
            FreeControllerV3 hip = controllers[hipID.val];
            vaginaPosition = hip.control.position;
            vaginaDirection = hip.control.forward;
        }
    }
    // Aligns female hips to penis direction
    private void AdaptiveHipsAlignment()
    {
        try
        {
            FreeControllerV3 penisBaseControl = controllers[penisBaseID.val];
            FreeControllerV3 penisMidControl = controllers.ContainsKey(penisMidID.val) ? controllers[penisMidID.val] : null;
            FreeControllerV3 penisTipControl = controllers[penisTipID.val];
            FreeControllerV3 femaleHipControl = controllers[hipID.val];
            if (penisBaseControl == null || penisTipControl == null || femaleHipControl == null) return;
            if (penisBaseControl.control == null || penisTipControl.control == null || femaleHipControl.control == null) return;
            // Calculate penis direction from base to tip
            Vector3 penisDirection = (penisBaseControl.control.position - penisTipControl.control.position).normalized;
            if (float.IsNaN(penisDirection.x) || float.IsNaN(penisDirection.y) || float.IsNaN(penisDirection.z))
            {
                SuperController.LogMessage($"{PluginName}: Invalid penis direction, skipping alignment");
                return;
            }
            // Adjust direction using mid controller if available and aligned
            if (penisMidControl != null && penisMidControl.control != null)
            {
                Vector3 tipToMid = (penisMidControl.control.position - penisTipControl.control.position).normalized;
                Vector3 midToBase = (penisBaseControl.control.position - penisMidControl.control.position).normalized;
                float alignmentAngle = Vector3.Angle(tipToMid, midToBase);
                if (alignmentAngle > 10f)
                {
                    SuperController.LogMessage($"{PluginName}: penisMidControl not aligned with base and tip, using tip-to-base direction");
                }
                else
                {
                    penisDirection = (tipToMid + midToBase).normalized;
                }
            }
            // Rotate hips to align with penis direction
            Quaternion currentRotation = femaleHipControl.control.rotation;
            Quaternion targetRotation = Quaternion.FromToRotation(currentRotation * Vector3.down, penisDirection) * currentRotation;
            Quaternion newRotation = Quaternion.Slerp(currentRotation, targetRotation, Time.deltaTime * rotationSpeed.val);
            femaleHipControl.control.rotation = newRotation;
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: AdaptiveHipsAlignment error: {e}");
        }
    }
    // Aligns pelvis position and rotation to hip controller
    private void UpdatePelvisAlignment()
    {
        try
        {
            if (!controllers.ContainsKey(hipID.val) || !controllers.ContainsKey(pelvisID.val)) return;
            FreeControllerV3 hipControl = controllers[hipID.val];
            FreeControllerV3 pelvisControl = controllers[pelvisID.val];
            if (hipControl == null || pelvisControl == null || hipControl.control == null || pelvisControl.control == null) return;
            // Position pelvis with offset from hip
            Vector3 offset = hipControl.control.rotation * Vector3.up * pelvisOffset.val;
            pelvisControl.control.position = hipControl.control.position + offset;
            pelvisControl.control.rotation = hipControl.control.rotation;
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: UpdatePelvisAlignment error: {e}");
        }
    }
    // Moves thighs towards penis tip with smooth interpolation
    private void UpdateThighMovement()
    {
        try
        {
            // Check if all required controllers are present
            if (!controllers.ContainsKey(leftThighID.val) || !controllers.ContainsKey(rightThighID.val) ||
                !controllers.ContainsKey(penisTipID.val) || !controllers.ContainsKey(hipID.val))
            {
                return;
            }
            FreeControllerV3 leftThigh = controllers[leftThighID.val];
            FreeControllerV3 rightThigh = controllers[rightThighID.val];
            FreeControllerV3 penisTip = controllers[penisTipID.val];
            FreeControllerV3 hip = controllers[hipID.val];
            if (leftThigh.control == null || rightThigh.control == null || penisTip.control == null || hip.control == null)
            {
                return;
            }
            // Calculate dynamic max distance and correction speed based on attraction strength
            float effectiveMaxDistance = Mathf.Lerp(0.2f, 1.0f, attractionStrength.val);
            float effectiveThighCorrectionSpeed = Mathf.Lerp(1.0f, 10.0f, attractionStrength.val);
            // Skip if penis is too far from vagina
            float distanceToVagina = Vector3.Distance(penisTip.control.position, vaginaPosition);
            if (distanceToVagina > effectiveMaxDistance)
            {
                return;
            }
            // Calculate directions to penis tip
            Vector3 toPenisLeft = (penisTip.control.position - lastLeftThighPos).normalized;
            Vector3 toPenisRight = (penisTip.control.position - lastRightThighPos).normalized;
            if (float.IsNaN(toPenisLeft.x) || float.IsNaN(toPenisRight.x))
            {
                return;
            }
            // Calculate target positions for thighs
            float thighDistance = 0.3f;
            Vector3 targetLeftPos = penisTip.control.position + toPenisLeft * thighDistance;
            Vector3 targetRightPos = penisTip.control.position + toPenisRight * thighDistance;
            // Calculate position deltas to prevent jittering
            float leftPosDelta = Vector3.Distance(leftThigh.control.position, targetLeftPos);
            float rightPosDelta = Vector3.Distance(rightThigh.control.position, targetRightPos);
            float smoothFactor = Time.deltaTime * effectiveThighCorrectionSpeed * 0.3f;
            // Move left thigh if delta is significant
            if (leftPosDelta > MinPositionDelta)
            {
                leftThigh.control.position = Vector3.Lerp(
                    leftThigh.control.position,
                    targetLeftPos,
                    smoothFactor
                );
                lastLeftThighPos = leftThigh.control.position;
            }
            // Move right thigh if delta is significant
            if (rightPosDelta > MinPositionDelta)
            {
                rightThigh.control.position = Vector3.Lerp(
                    rightThigh.control.position,
                    targetRightPos,
                    smoothFactor
                );
                lastRightThighPos = rightThigh.control.position;
            }
            // Align thigh rotations with hip
            Quaternion targetLeftRot = Quaternion.LookRotation(hip.control.forward, hip.control.up);
            Quaternion targetRightRot = Quaternion.LookRotation(hip.control.forward, hip.control.up);
            float leftRotDelta = Quaternion.Angle(leftThigh.control.rotation, targetLeftRot);
            float rightRotDelta = Quaternion.Angle(rightThigh.control.rotation, targetRightRot);
            // Rotate left thigh if delta is significant
            if (leftRotDelta > MinRotationDelta)
            {
                leftThigh.control.rotation = Quaternion.Slerp(
                    leftThigh.control.rotation,
                    targetLeftRot,
                    smoothFactor
                );
                lastLeftThighRot = leftThigh.control.rotation;
            }
            // Rotate right thigh if delta is significant
            if (rightRotDelta > MinRotationDelta)
            {
                rightThigh.control.rotation = Quaternion.Slerp(
                    rightThigh.control.rotation,
                    targetRightRot,
                    smoothFactor
                );
                lastRightThighRot = rightThigh.control.rotation;
            }
        }
        catch (Exception e)
        {
            SuperController.LogError($"{PluginName}: UpdateThighMovement error: {e}");
        }
    }
    // Updates the status text with alignment information
    private void UpdateStatusText()
    {
        try
        {
            if (!hasValidTargets)
            {
                statusText.val = "Error: Required controllers not found";
                return;
            }
            FreeControllerV3 penisTip = controllers[penisTipID.val];
            FreeControllerV3 penisBase = controllers[penisBaseID.val];
            FreeControllerV3 hip = controllers[hipID.val];
            if (penisTip == null || penisBase == null || hip == null || penisTip.control == null || penisBase.control == null || hip.control == null)
            {
                statusText.val = "Error: Controllers not initialized";
                return;
            }
            // Calculate and display alignment metrics
            float distance = Vector3.Distance(penisTip.control.position, hip.control.position);
            Vector3 angles = hip.control.rotation.eulerAngles;
            Vector3 downDirection = hip.control.rotation * Vector3.down;
            Vector3 penisDirection = (penisBase.control.position - penisTip.control.position).normalized;
            float altitudeAngle = Vector3.Angle(downDirection, penisDirection);
            statusText.val = $"Расстояние: {distance:F2} м\n" +
                             $"Углы бедра: X={angles.x:F1}° Y={angles.y:F1}° Z={angles.z:F1}°\n" +
                             $"Выравнивание: {alignmentAngle:F1}°\n" +
                             $"Сила притяжения: {attractionStrength.val:F2}\n" +
                             $"Мужской: {maleAtom?.uid ?? "Нет"}\n" +
                             $"Женский: {femaleAtom?.uid ?? "Нет"}";
        }
        поймать (исключение e)
        {
            SuperController.LogError($"{PluginName}: Ошибка UpdateStatusText: {e}");
        }
    }
    // Включает все женские контрольные точки с помощью нажатия кнопки
    private void EnableAllPoints()
    {
        пытаться
        {
            SuperController.LogMessage($"{PluginName}: Включение всех женских контрольных точек");
            контроллеры.Очистить();
            hasValidTargets = false;
            enableAlignment.val = true; // Включить переключение выравнивания
            ИнициализироватьКонтроллеры(); // Повторно инициализируем контроллеры
            если (hasValidTargets)
            {
                SuperController.LogMessage($"{PluginName}: EnableAllPoints выполнен успешно");
            }
            еще
            {
                SuperController.LogError($"{PluginName}: Ошибка EnableAllPoints");
            }
        }
        поймать (исключение e)
        {
            SuperController.LogError($"{PluginName}: Ошибка EnableAllPoints: {e}");
            statusText.val = "Ошибка включения точек";
        }
    }
    // Вызывается при выгрузке или перезагрузке плагина, очищает контроллеры без изменения состояний
    public void OnDestroy()
    {
        пытаться
        {
            SuperController.LogMessage($"{PluginName}: Вызван OnDestroy, очистка контроллеров без изменения состояний");
            controllers.Clear(); // Очистить словарь контроллера, не трогая состояния контроллера
        }
        поймать (исключение e)
        {
            SuperController.LogError($"{PluginName}: Ошибка OnDestroy: {e}");
        }
    }
}
[/СПОЙЛЕР]