using System; using UnityEngine; using System.Collections.Generic; namespace ky1001 { public class Searchlight : MVRScript { #region PluginInfo public string pluginAuthor = "ky1001"; public string pluginName = "Searchlight"; public string pluginVersion = "13.0"; public string pluginDate = "2025-08-13"; public string pluginLicense = "CC BY-SA"; public string pluginDescription = ""; #endregion #region Var private UIDynamicToggle en_Toggle; private JSONStorableBool en_Bool; private UIDynamicToggle rev_Toggle; private JSONStorableBool rev_Bool; private UIDynamicToggle line_Toggle; private JSONStorableBool line_Bool; private JSONStorableBool vu_Bool; private UIDynamicToggle mv_Toggle; private JSONStorableBool mv_Bool; protected JSONStorableStringChooser vectorJSON; protected JSONStorableStringChooser atomJSON; protected JSONStorableStringChooser receiverJSON; private JSONStorableStringChooser _axisJSON; protected UIDynamicPopup dp; private JSONStorableFloat X; private JSONStorableFloat Y; private JSONStorableFloat Z; private UIDynamicSlider sX; private UIDynamicSlider sY; private UIDynamicSlider sZ; private JSONStorableFloat Xc; private JSONStorableFloat Yc; private JSONStorableFloat Zc; private UIDynamicSlider sXc; private UIDynamicSlider sYc; private UIDynamicSlider sZc; private JSONStorableFloat tDZx; private JSONStorableFloat tDZy; private JSONStorableFloat tDZz; private UIDynamicSlider stDZx; private UIDynamicSlider stDZy; private UIDynamicSlider stDZz; private JSONStorableFloat mvFx; private UIDynamicSlider smvFx; private JSONStorableFloat mvFy; private UIDynamicSlider smvFy; private JSONStorableFloat mvFz; private UIDynamicSlider smvFz; Vector3 targetPastPosition; Vector3 targetPosition; Vector3 currentPastPosition; Vector3 v3; Vector3 targetAtomPosition; Vector3 currentAtomPosition; Vector3 aim; Quaternion look; Atom targetAtom; protected Atom receivingAtom; GameObject line = null; private float pastDZx; private float pastDZy; private float pastDZz; private JSONStorableBool _isKeepHorToCam; private JSONStorableBool _isFixedUpdate; private readonly string[] _rigidbodyNames = new string[] { "hip", "pelvis", "rThigh", "rShin", "rFoot", "rToe", "LGlute", "RGlute", "lThigh", "lShin", "lFoot", "lToe", "abdomen", "abdomen2", "chest", "rCollar", "rShldr", "rForeArm", "rHand", "lPectoral", "lNipple", "rPectoral", "rNipple", "neck", "head", "lCollar", "lShldr", "lForeArm", "lHand" }; #endregion public override void Init() { try { // put init code in here en_Bool = new JSONStorableBool("Enable Trace Target", true); en_Toggle = CreateToggle(en_Bool, false); rev_Bool = new JSONStorableBool("Reverse", false); rev_Toggle = CreateToggle(rev_Bool, false); line_Bool = new JSONStorableBool("Draw Line", false); line_Toggle = CreateToggle(line_Bool, false); _isKeepHorToCam = new JSONStorableBool("Keep Horizontal to Camera", false); CreateToggle(_isKeepHorToCam, false); _isFixedUpdate = new JSONStorableBool("Select FixedUpdate", false); CreateToggle(_isFixedUpdate); List axisChoices = new List { "X", "Y", "Z" }; List vectorChoices = new List { "None", "up", "down", "left", "right", "forward", "back" }; vu_Bool = new JSONStorableBool("Vector3 up", true); //vu_Toggle = CreateToggle(vu_Bool, false); var spacer = CreateSpacer(false); spacer.height = 100f; tDZx = new JSONStorableFloat("Target Dead Zone X adjustment", 0f, 0f, 10.0f, true); stDZx = CreateSlider(tDZx, false); tDZy = new JSONStorableFloat("Target Dead Zone Y adjustment", 0f, 0f, 10.0f, true); stDZy = CreateSlider(tDZy, false); tDZz = new JSONStorableFloat("Target Dead Zone Z adjustment", 0f, 0f, 10.0f, true); stDZz = CreateSlider(tDZz, false); spacer = CreateSpacer(false); spacer.height = 0f; mv_Bool = new JSONStorableBool("Moving Factor", false); mv_Toggle = CreateToggle(mv_Bool, false); mvFx = new JSONStorableFloat("X Moving Factor", 1.0f, 0f, 1.0f, true); smvFx = CreateSlider(mvFx, false); mvFy = new JSONStorableFloat("Y Moving Factor", 1.0f, 0f, 1.0f, true); smvFy = CreateSlider(mvFy, false); mvFz = new JSONStorableFloat("Z Moving Factor", 1.0f, 0f, 1.0f, true); smvFz = CreateSlider(mvFz, false); mkList(); receiverJSON = new JSONStorableStringChooser("Target Receiver", null, "None", "Target Receiver"); dp = CreateScrollablePopup(receiverJSON, true); dp.popupPanelHeight = 960f; dp.popup.selectColor = new Color(0.8f, 0.8f, 0.8f); _axisJSON = new JSONStorableStringChooser("Select Axis", axisChoices, "Z", "Select Axis"); CreatePopup(_axisJSON, true); vectorJSON = new JSONStorableStringChooser("Vector3 direction", vectorChoices, "None", "Vector3 direction"); dp = CreateScrollablePopup(vectorJSON, true); dp.popupPanelHeight = 460f; dp.popup.selectColor = new Color(0.8f, 0.8f, 0.8f); X = new JSONStorableFloat("Target X adjustment", 0f, -2.0f, 2.0f, true); sX = CreateSlider(X, true); Y = new JSONStorableFloat("Target Y adjustment", 0f, -2.0f, 2.0f, true); sY = CreateSlider(Y, true); Z = new JSONStorableFloat("Target Z adjustment", 0f, -2.0f, 2.0f, true); sZ = CreateSlider(Z, true); spacer = CreateSpacer(true); spacer.height = 65f; Xc = new JSONStorableFloat("Current X adjustment", 0f, -2.0f, 2.0f, true); sXc = CreateSlider(Xc, true); Yc = new JSONStorableFloat("Current Y adjustment", 0f, -2.0f, 2.0f, true); sYc = CreateSlider(Yc, true); Zc = new JSONStorableFloat("Current Z adjustment", 0f, -2.0f, 2.0f, true); sZc = CreateSlider(Zc, true); RegisterBool(en_Bool); RegisterBool(rev_Bool); RegisterBool(line_Bool); RegisterBool(_isKeepHorToCam); RegisterBool(_isFixedUpdate); RegisterStringChooser(atomJSON); RegisterStringChooser(receiverJSON); RegisterStringChooser(vectorJSON); RegisterBool(vu_Bool); RegisterFloat(tDZx); RegisterFloat(tDZy); RegisterFloat(tDZz); RegisterBool(mv_Bool); RegisterFloat(mvFx); RegisterFloat(mvFy); RegisterFloat(mvFz); RegisterFloat(X); RegisterFloat(Y); RegisterFloat(Z); RegisterFloat(Xc); RegisterFloat(Yc); RegisterFloat(Zc); } catch (Exception e) { SuperController.LogError("Exception caught: " + e); } } // make atom selector public void mkList() { List targetChoices = new List(); foreach (string atomUID in SuperController.singleton.GetAtomUIDs()) { targetAtom = SuperController.singleton.GetAtomByUid(atomUID); if (targetAtom != containingAtom && targetAtom.hidden == false && targetAtom.GetBoolJSONParam("on").val == true && targetAtom.name != "CoreControl") { targetChoices.Add(atomUID); } } atomJSON = new JSONStorableStringChooser("Target Atom", targetChoices, "None", "Target Atom", SyncAtom); dp = CreateScrollablePopup(atomJSON, true); dp.popupPanelHeight = 1100f; dp.popup.selectColor = new Color(0.8f, 0.8f, 0.8f); } // receiver Atom protected void SyncAtom(string atomUID) { List receiverChoices = new List(); if (atomUID != null) { receivingAtom = SuperController.singleton.GetAtomByUid(atomUID); if (receivingAtom != null) { FreeControllerV3[] controls = receivingAtom.freeControllers; foreach (var receiverChoice in controls) { string temp_string = receiverChoice.ToString(); temp_string = temp_string.Substring(0, temp_string.Length - 19); if (receivingAtom.GetStorableByID(temp_string) != null) { receiverChoices.Add(temp_string); } } if (atomUID == "[CameraRig]") { receiverChoices.Add("CenterEye"); receiverChoices.Add("LeftHand"); receiverChoices.Add("RightHand"); } if (receivingAtom.type == "Person") { foreach (var item in _rigidbodyNames) { receiverChoices.Add(item); } receiverChoices.Add("LipTrigger"); receiverChoices.Add("MouthTrigger"); receiverChoices.Add("ThroatTrigger"); receiverChoices.Add("LabiaTrigger"); receiverChoices.Add("VaginaTrigger"); receiverChoices.Add("DeepVaginaTrigger"); receiverChoices.Add("DeeperVaginaTrigger"); } } } else { receivingAtom = null; } receiverJSON.choices = receiverChoices; receiverJSON.val = "None"; } private void AimControl() { if (vectorJSON.val == "up") { v3 = Vector3.up; } if (vectorJSON.val == "down") { v3 = Vector3.down; } if (vectorJSON.val == "left") { v3 = Vector3.left; } if (vectorJSON.val == "right") { v3 = Vector3.right; } if (vectorJSON.val == "forward") { v3 = Vector3.forward; } if (vectorJSON.val == "back") { v3 = Vector3.back; } if (vectorJSON.val == "None" && vu_Bool.val == true) { v3 = Vector3.up; } if (vectorJSON.val == "None" && vu_Bool.val == false) { v3 = Vector3.forward; } if (line != null) { Destroy(line); } if (mv_Bool.val) { pastDZx = mvFx.val; pastDZy = mvFy.val; pastDZz = mvFz.val; } else { pastDZx = 1.0f; pastDZy = 1.0f; pastDZz = 1.0f; } if (atomJSON.val != "None" && receiverJSON.val != "None" && en_Bool.val == true) { if (atomJSON.val != "[CameraRig]") { targetAtomPosition = receivingAtom.GetStorableByID(receiverJSON.val).transform.position; } else { if (receiverJSON.val == "CenterEye") { targetAtomPosition = SuperController.singleton.lookCamera.transform.position; } if (receiverJSON.val == "LeftHand") { targetAtomPosition = SuperController.singleton.leftHand.transform.position; } if (receiverJSON.val == "RightHand") { targetAtomPosition = SuperController.singleton.rightHand.transform.position; } } Vector3 adjPosition; adjPosition.x = X.val; adjPosition.y = Y.val; adjPosition.z = Z.val; currentAtomPosition = containingAtom.GetStorableByID("control").transform.position; Vector3 adjcurrentPosition; adjcurrentPosition.x = Xc.val; adjcurrentPosition.y = Yc.val; adjcurrentPosition.z = Zc.val; Vector3 targetDiff; targetDiff = targetAtomPosition - targetPastPosition; if (Mathf.Abs(targetDiff.x) >= tDZx.val) { targetPosition.x = targetPastPosition.x + targetDiff.x * pastDZx; } if (Mathf.Abs(targetDiff.y) >= tDZy.val) { targetPosition.y = targetPastPosition.y + targetDiff.y * pastDZy; } if (Mathf.Abs(targetDiff.z) >= tDZz.val) { targetPosition.z = targetPastPosition.z + targetDiff.z * pastDZz; } if (atomJSON.val == "[CameraRig]" && _isKeepHorToCam.val) { Transform cameraRig = SuperController.singleton.lookCamera.transform; aim = (cameraRig.position + adjPosition) - (cameraRig.forward + adjcurrentPosition) * 10000000.0f; } else { aim = (targetPosition + adjPosition) - (currentAtomPosition + adjcurrentPosition); } look = Quaternion.LookRotation(aim, v3); //containingAtom.GetStorableByID("control").transform.localRotation = look; containingAtom.GetStorableByID("control").transform.rotation = look; if (rev_Bool.val) { Quaternion rev = Quaternion.Euler(0f, 180f, 0f); //containingAtom.GetStorableByID("control").transform.localRotation *= rev; containingAtom.GetStorableByID("control").transform.rotation *= rev; } Quaternion selectedAxis = Quaternion.Euler(0f, 0f, 0f); if (_axisJSON.val == "Y") { selectedAxis = Quaternion.Euler(90f, 0f, 0f); } else if (_axisJSON.val == "X") { selectedAxis = Quaternion.Euler(0f, -90f, 0f); } //containingAtom.GetStorableByID("control").transform.localRotation *= rev; containingAtom.GetStorableByID("control").transform.rotation *= selectedAxis; if (line_Bool.val == true) { if (line != null) { Destroy(line); } line = new GameObject(); var rd = line.AddComponent(); var positions = new Vector3[] { currentAtomPosition + adjcurrentPosition, targetPosition + adjPosition }; rd.startWidth = 0.01f; rd.endWidth = 0.01f; rd.SetPositions(positions); } } targetPastPosition = targetPosition; } // Start is called once before Update or FixedUpdate is called and after Init() void Start() { try { List targetChoices = new List(); targetChoices = SuperController.singleton.GetAtomUIDs(); if (!targetChoices.Contains(atomJSON.val)) { atomJSON.val = "None"; receiverJSON.val = "None"; } } catch (Exception e) { SuperController.LogError("Exception caught: " + e); } } // Update is called with each rendered frame by Unity void Update() { try { if (_isFixedUpdate.val) { return; } AimControl(); } catch (Exception e) { SuperController.LogError($"{nameof(Searchlight)}.{nameof(Update)}: {e}"); } } // FixedUpdate is called with each physics simulation frame by Unity void FixedUpdate() { try { if (!_isFixedUpdate.val) { return; } AimControl(); } catch (Exception e) { SuperController.LogError($"{nameof(Searchlight)}.{nameof(FixedUpdate)}: {e}"); } } public void OnDisable() { try { if (line != null) { Destroy(line); } } catch (Exception e) { SuperController.LogError($"{nameof(Searchlight)}.{nameof(OnDisable)}: {e}"); } } // OnDestroy is where you should put any cleanup // if you registered objects to SuperController or atom, you should unregister them here void OnDestroy() { if (line != null) { Destroy(line); } } } }