using System; using System.Collections.Generic; using UnityEngine; using System.Linq; namespace Redeyes { public class DiviningForeskin : MVRScript { protected struct OtherPersonInfo { public Atom PersonAtom; public bool IsFemale; public Component MouthTarget; public Component VaginaTarget; public Component AnusTarget; public Component LHandTarget; public Component RHandTarget; public Trigger MouthTrigger; public Trigger VaginaTrigger; public Trigger AnusTrigger; public Trigger RHandTrigger; public Trigger LHandTrigger; } protected List _otherPersons = new List(); protected FreeControllerV3 _penisBase; protected FreeControllerV3 _penisMid; protected FreeControllerV3 _penisTip; protected Rigidbody _gen1; protected Rigidbody _gen2; protected Rigidbody _gen3; protected Rigidbody _pelvis; protected Component _penisTarget; protected Trigger _targetTrigger; protected string _targetType; protected Vector3 _targetOffset; protected float _targetDistance; protected Atom _targetAtom = null; protected Component _foreskinTarget; protected Trigger _foreskintargetTrigger; protected string _foreskintargetType; protected Vector3 _foreskintargetOffset; protected float _foreskintargetDistance; protected Atom _foreskintargetAtom = null; protected float _foreSkinTargetFromBaseDistance; protected float _targetTipToBaseDistance; protected float A; protected float B; protected float C; protected float D; protected float foreskin_amount; protected float foreskin_amount_2x; protected float foreskin_amount_negative; protected float foreskin_G2MGensSkinMove_amount; protected bool _shouldBeInsideTarget; protected bool _isInsideTarget; protected JSONStorableBool _mouthEnabledJSON = new JSONStorableBool("Target Mouth", true); protected JSONStorableBool _vaginaEnabledJSON = new JSONStorableBool("Target Vagina", true); protected JSONStorableBool _anusEnabledJSON = new JSONStorableBool("Target Anus", false); protected JSONStorableBool _rHandEnabledJSON = new JSONStorableBool("Target Right Hand", true); protected JSONStorableBool _lHandEnabledJSON = new JSONStorableBool("Target Left Hand", true); protected JSONStorableBool _femaleEnabledJSON = new JSONStorableBool("Target Females", true); protected JSONStorableBool _maleEnabledJSON = new JSONStorableBool("Target Males", true); protected JSONStorableBool _foreskinPullEnabledJSON = new JSONStorableBool("Foreskin Pullback", true); protected JSONStorableFloat _foreskinPullMinAmountJSON = new JSONStorableFloat("Foreskin Pull Distance", 0.04f, 0f, 0.1f, false); protected JSONStorableFloat _foreskinPullMaxAmountJSON = new JSONStorableFloat("Foreskin Length Limit", 0.02f, 0f, 0.4f, false); protected JSONStorableFloat _foreskinPullHandFromBaseOffsetJSON = new JSONStorableFloat("Pullback Hand Offset", 0.03f, -0.1f, 0.1f, false); protected JSONStorableFloat _foreskinPullVagAnusFromBaseOffsetJSON = new JSONStorableFloat("Pullback Vag/Anus Offset", 0.09f, 0.04f, 0.15f, false); protected JSONStorableFloat _foreskinPullMouthFromBaseOffsetJSON = new JSONStorableFloat("Pullback Mouth Offset", 0.07f, 0.04f, 0.12f, false); protected JSONStorableFloat _G2MGensSkinMoveAmountJSON = new JSONStorableFloat("G2MGensSkinMove Amount", 1.0f, -0.4f, 2.0f, false); protected JSONStorableStringChooser _foreskinMorpheChooser; protected List _foreskinMorpheChoices = new List(); protected string _foreskinMorphe; protected JSONStorableStringChooser _penisBasePositionContolChooser; protected List _penisBasePositionContolChoices = new List(); protected JSONStorableStringChooser _penisBaseRotationContolChooser; protected List _penisBaseRotationContolChoices = new List(); protected JSONStorableFloat _minTargetDistanceJSON = new JSONStorableFloat("Min Target Distance [cm]", 50f, 0f, 100f, false); protected JSONStorableFloat _minRetargetDistanceJSON = new JSONStorableFloat("Min Retarget Distance [cm]", 10f, 0f, 100f, false); protected JSONStorableFloat _adaptationRateJSON = new JSONStorableFloat("Adaptation Rate [%/s]", 99.9f, 0f, 100f, true); protected JSONStorableFloat _tipForceJSON = new JSONStorableFloat("Force Tip To Target", 0f, 0f, 10f, false); protected JSONStorableBool _dynamicAdaptionRateJSON = new JSONStorableBool("Dynamic Adaptation Rate", true); protected JSONStorableBool _avoidPopOutJSON = new JSONStorableBool("Avoid Pop Out", false); protected JSONStorableBool _verifyWithTriggerJSON = new JSONStorableBool("Verify With Trigger", true); protected JSONStorableBool _instantPopBackJSON = new JSONStorableBool("Instant Pop Back", true); protected JSONStorableFloat _popOutDetectionThresholdJSON = new JSONStorableFloat("Pop Out Detection Threshold [°]", 10f, 0f, 90f, true); protected JSONStorableFloat _mouthOffsetXJSON = new JSONStorableFloat("Mouth Offset X [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _mouthOffsetYJSON = new JSONStorableFloat("Mouth Offset Y [cm]", -1.5f, -10f, 10f, false); protected JSONStorableFloat _mouthOffsetZJSON = new JSONStorableFloat("Mouth Offset Z [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _vaginaOffsetXJSON = new JSONStorableFloat("Vagina Offset X [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _vaginaOffsetYJSON = new JSONStorableFloat("Vagina Offset Y [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _vaginaOffsetZJSON = new JSONStorableFloat("Vagina Offset Z [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _anusOffsetXJSON = new JSONStorableFloat("Anus Offset X [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _anusOffsetYJSON = new JSONStorableFloat("Anus Offset Y [cm]", 0.3f, -10f, 10f, false); protected JSONStorableFloat _anusOffsetZJSON = new JSONStorableFloat("Anus Offset Z [cm]", -3f, -10f, 10f, false); protected JSONStorableFloat _handOffsetXJSON = new JSONStorableFloat("Hand Offset X [cm]", 7f, -10f, 10f, false); protected JSONStorableFloat _handOffsetYJSON = new JSONStorableFloat("Hand Offset Y [cm]", -1.6f, -10f, 10f, false); protected JSONStorableFloat _handOffsetZJSON = new JSONStorableFloat("Hand Offset Z [cm]", 2f, -10f, 10f, false); protected JSONStorableFloat _customTargetOffsetXJSON = new JSONStorableFloat("Custom Target Offset X [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _customTargetOffsetYJSON = new JSONStorableFloat("Custom Target Offset Y [cm]", 0f, -10f, 10f, false); protected JSONStorableFloat _customTargetOffsetZJSON = new JSONStorableFloat("Custom Target Offset Z [cm]", 0f, -10f, 10f, false); protected JSONStorableStringChooser _customTargetAtomChooser; protected UIDynamicPopup _customTargetAtomPopup; protected JSONStorableStringChooser _customTargetChooser; protected UIDynamicPopup _customTargetPopup; protected Atom _customTargetAtom = null; protected Component _customTarget = null; protected DAZCharacterSelector _selector; protected static DAZMorph foreskinmorphe_choice; protected static DAZMorph Frsk_Scale_Point; protected static DAZMorph Frsk_Globe; protected static DAZMorph GI_big; protected static DAZMorph GI_small; protected static DAZMorph PBMFS01; protected static DAZMorph G2MGensSkinMove; protected void syncCustomTarget() { if (_penisTarget == _customTarget) { _penisTarget = null; // reset } if (string.IsNullOrEmpty(_customTargetAtomChooser.val) || _customTargetAtomChooser.val == "None") { _customTargetAtom = null; _customTarget = null; } else { _customTargetAtom = SuperController.singleton.GetAtomByUid(_customTargetAtomChooser.val); if (string.IsNullOrEmpty(_customTargetChooser.val) || _customTargetChooser.val == "None") { _customTarget = null; } else { _customTarget = _customTargetAtom.rigidbodies.FirstOrDefault(rb => rb.name == _customTargetChooser.val); } } } protected List getCustomTargetAtomChoices() { List choices = new List{"None"}; choices.AddRange(SuperController.singleton.GetAtomUIDsWithRigidbodies()); return choices; } protected List getCustomTargetChoices() { List choices = new List{"None"}; if (!string.IsNullOrEmpty(_customTargetAtomChooser.val) && _customTargetAtomChooser.val != "None") { choices.AddRange(SuperController.singleton.GetRigidbodyNamesInAtom(_customTargetAtomChooser.val)); } return choices; } protected void syncOtherPersonAtoms() { _penisTarget = null; // reset target if (!(_maleEnabledJSON.val || _femaleEnabledJSON.val)) { _otherPersons = new List(); // empty list } else { List otherPersonAtoms; otherPersonAtoms = SuperController.singleton.GetAtoms().Where(atom => atom.type == "Person").ToList(); //&& atom != containingAtom).ToList(); if (_femaleEnabledJSON.val && !_maleEnabledJSON.val) { otherPersonAtoms = otherPersonAtoms.Where(atom => IsFemale(atom)).ToList(); } else if (!_femaleEnabledJSON.val && _maleEnabledJSON.val) { otherPersonAtoms = otherPersonAtoms.Where(atom => !IsFemale(atom)).ToList(); } _otherPersons.Clear(); foreach (Atom personAtom in otherPersonAtoms) { OtherPersonInfo personInfo; personInfo.PersonAtom = personAtom; personInfo.IsFemale = IsFemale(personAtom); personInfo.MouthTarget = personAtom.rigidbodies.First(rb => rb.name == "LipTrigger"); personInfo.VaginaTarget = personAtom.rigidbodies.First(rb => rb.name == "LabiaTrigger"); personInfo.AnusTarget = personInfo.VaginaTarget; personInfo.RHandTarget = personAtom.rigidbodies.First(rb => rb.name == "rHand"); personInfo.LHandTarget = personAtom.rigidbodies.First(rb => rb.name == "lHand"); personInfo.MouthTrigger = personAtom.rigidbodies.First(rb => rb.name == "MouthTrigger").GetComponent().trigger; personInfo.VaginaTrigger = personAtom.rigidbodies.First(rb => rb.name == "VaginaTrigger").GetComponent().trigger; personInfo.AnusTrigger = personInfo.VaginaTrigger; personInfo.RHandTrigger = null; personInfo.LHandTrigger = null; _otherPersons.Add(personInfo); } } } public override void Init() { UIDynamicTextField text = null; try { //TARGET CONTROLS _mouthEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _mouthEnabledJSON.setCallbackFunction += (bool isChecked) => { if (_targetType == "Mouth" && !isChecked) { _penisTarget = null; } }; RegisterBool(_mouthEnabledJSON); CreateToggle(_mouthEnabledJSON, false); _vaginaEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _vaginaEnabledJSON.setCallbackFunction += (bool isChecked) => { if (_targetType == "Vagina" && !isChecked) { _penisTarget = null; } }; RegisterBool(_vaginaEnabledJSON); CreateToggle(_vaginaEnabledJSON, false); _anusEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _anusEnabledJSON.setCallbackFunction += (bool isChecked) => { if (_targetType == "Anus" && !isChecked) { _penisTarget = null; } }; RegisterBool(_anusEnabledJSON); CreateToggle(_anusEnabledJSON, false); _rHandEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _rHandEnabledJSON.setCallbackFunction += (bool isChecked) => { if (_targetType == "rHand" && !isChecked) { _penisTarget = null; } }; RegisterBool(_rHandEnabledJSON); CreateToggle(_rHandEnabledJSON, false); _lHandEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _lHandEnabledJSON.setCallbackFunction += (bool isChecked) => { if (_targetType == "lHand" && !isChecked) { _penisTarget = null; } }; RegisterBool(_lHandEnabledJSON); CreateToggle(_lHandEnabledJSON, false); //PENIS CONTROLS _foreskinPullEnabledJSON.storeType = JSONStorableParam.StoreType.Full; RegisterBool(_foreskinPullEnabledJSON); CreateToggle(_foreskinPullEnabledJSON, true); _foreskinPullHandFromBaseOffsetJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_foreskinPullHandFromBaseOffsetJSON); CreateSlider(_foreskinPullHandFromBaseOffsetJSON, true); _foreskinPullVagAnusFromBaseOffsetJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_foreskinPullVagAnusFromBaseOffsetJSON); CreateSlider(_foreskinPullVagAnusFromBaseOffsetJSON, true); _foreskinPullMouthFromBaseOffsetJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_foreskinPullMouthFromBaseOffsetJSON); CreateSlider(_foreskinPullMouthFromBaseOffsetJSON, true); _foreskinPullMinAmountJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_foreskinPullMinAmountJSON); CreateSlider(_foreskinPullMinAmountJSON, true); _foreskinPullMaxAmountJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_foreskinPullMaxAmountJSON); CreateSlider(_foreskinPullMaxAmountJSON, true); _G2MGensSkinMoveAmountJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_G2MGensSkinMoveAmountJSON); CreateSlider(_G2MGensSkinMoveAmountJSON, true); _foreskinMorpheChoices.Add("PBMFS02"); _foreskinMorpheChoices.Add("PBMFS03"); _foreskinMorpheChoices.Add("PBMFS04"); _foreskinMorpheChoices.Add("PBMFS05"); _foreskinMorpheChooser = new JSONStorableStringChooser("Foreskin Morphes", _foreskinMorpheChoices, "PBMFS04", "Foreskin Morphe"); RegisterStringChooser(_foreskinMorpheChooser); UIDynamicPopup _foreskinMorphePopup = CreatePopup(_foreskinMorpheChooser, true); _penisBasePositionContolChoices.Add("On"); _penisBasePositionContolChoices.Add("Comply"); _penisBasePositionContolChoices.Add("Off"); _penisBasePositionContolChoices.Add("ParentLink"); _penisBasePositionContolChoices.Add("PhysicsLink"); _penisBasePositionContolChooser = new JSONStorableStringChooser("Penis Base Position Control", _penisBasePositionContolChoices, "Off", "Penis Position Base Control"); RegisterStringChooser(_penisBasePositionContolChooser); UIDynamicPopup _penisPostionBaseContolPopup = CreatePopup(_penisBasePositionContolChooser, true); _penisBaseRotationContolChoices.Add("Plugin Controlled"); _penisBaseRotationContolChoices.Add("On"); _penisBaseRotationContolChoices.Add("Comply"); _penisBaseRotationContolChoices.Add("Off"); _penisBaseRotationContolChoices.Add("ParentLink"); _penisBaseRotationContolChoices.Add("PhysicsLink"); _penisBaseRotationContolChooser = new JSONStorableStringChooser("Penis Rotation Control", _penisBaseRotationContolChoices, "Plugin Controlled", "Penis Rotation Control"); RegisterStringChooser(_penisBaseRotationContolChooser); UIDynamicPopup _penisRotationBaseContolPopup = CreatePopup(_penisBaseRotationContolChooser, true); CreateSpacer(false).height = 20f; //TARGET GENDER CONTROLS _femaleEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _femaleEnabledJSON.setCallbackFunction += (bool isChecked) => syncOtherPersonAtoms(); RegisterBool(_femaleEnabledJSON); CreateToggle(_femaleEnabledJSON, false); _maleEnabledJSON.storeType = JSONStorableParam.StoreType.Full; _maleEnabledJSON.setCallbackFunction += (bool isChecked) => syncOtherPersonAtoms(); RegisterBool(_maleEnabledJSON); CreateToggle(_maleEnabledJSON, false); CreateSpacer(false).height = 30f; _customTargetAtomChooser = new JSONStorableStringChooser("customTargetAtom", getCustomTargetAtomChoices(), "None", "Custom Target Atom", (string name) => { _customTargetChooser.SetVal("None"); syncCustomTarget(); }); RegisterStringChooser(_customTargetAtomChooser); _customTargetAtomPopup = CreateFilterablePopup(_customTargetAtomChooser); _customTargetAtomPopup.popup.onOpenPopupHandlers += () => { _customTargetAtomChooser.choices = getCustomTargetAtomChoices(); }; _customTargetChooser = new JSONStorableStringChooser("customTarget", getCustomTargetChoices(), "None", "Custom Target", (string name) => syncCustomTarget()); RegisterStringChooser(_customTargetChooser); _customTargetPopup = CreateFilterablePopup(_customTargetChooser); _customTargetPopup.popup.onOpenPopupHandlers += () => { _customTargetChooser.choices = getCustomTargetChoices(); }; CreateSpacer(false).height = 70f; _minTargetDistanceJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_minTargetDistanceJSON); CreateSlider(_minTargetDistanceJSON, false); _minRetargetDistanceJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_minRetargetDistanceJSON); CreateSlider(_minRetargetDistanceJSON, false); CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _adaptationRateJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_adaptationRateJSON); CreateSlider(_adaptationRateJSON, false); _dynamicAdaptionRateJSON.storeType = JSONStorableParam.StoreType.Full; RegisterBool(_dynamicAdaptionRateJSON); CreateToggle(_dynamicAdaptionRateJSON, false); text = CreateTextField(new JSONStorableString("text", "Percentage of adaptation per second (100 = instant). " + "If dynamic adaptation rate is on, the effective rate will be 0 " + "at \"Min Target Distance\", and reach the given value at " + "\"Min Retarget Distance\"." ), true); text.height = 185f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _avoidPopOutJSON.storeType = JSONStorableParam.StoreType.Full; RegisterBool(_avoidPopOutJSON); CreateToggle(_avoidPopOutJSON, false); _popOutDetectionThresholdJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_popOutDetectionThresholdJSON); CreateSlider(_popOutDetectionThresholdJSON, false); _verifyWithTriggerJSON.storeType = JSONStorableParam.StoreType.Full; RegisterBool(_verifyWithTriggerJSON); CreateToggle(_verifyWithTriggerJSON, false); _instantPopBackJSON.storeType = JSONStorableParam.StoreType.Full; RegisterBool(_instantPopBackJSON); CreateToggle(_instantPopBackJSON, false); text = CreateTextField(new JSONStorableString("text", "(Experimental) Active penis pop out prevention. " + "Will briefly disable penis collision if pop out is detected. " + "The detection threshold is based on the angle between penis and target. " + "\"Verify With Trigger\" will make the detection much more stable (recommended), " + "but will limit pop out prevention to mouth, vagina, and anus. " + "It will then also not count as a pop out if you just get into the wrong hole. " + "\"Instant Pop Back\" will fix adaptation rate to 100 during pop back." ), true); text.height = 315f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _tipForceJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_tipForceJSON); CreateSlider(_tipForceJSON, false); text = CreateTextField(new JSONStorableString("text", "(Experimental) An additional continuous force and torque on the penis tip towards the target. " + "Can help sliding it in at particular steep angles." ), true); text.height = 120f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _mouthOffsetXJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_mouthOffsetXJSON); CreateSlider(_mouthOffsetXJSON, false); _mouthOffsetYJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_mouthOffsetYJSON); CreateSlider(_mouthOffsetYJSON, false); _mouthOffsetZJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_mouthOffsetZJSON); CreateSlider(_mouthOffsetZJSON, false); text = CreateTextField(new JSONStorableString("text", "Mouth target position relative to LipTrigger.\n" + "\nX: right from the view of the target." + "\nY: up towards nose." + "\nZ: forward outside of mouth." ), true); text.height = 390f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _vaginaOffsetXJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_vaginaOffsetXJSON); CreateSlider(_vaginaOffsetXJSON, false); _vaginaOffsetYJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_vaginaOffsetYJSON); CreateSlider(_vaginaOffsetYJSON, false); _vaginaOffsetZJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_vaginaOffsetZJSON); CreateSlider(_vaginaOffsetZJSON, false); text = CreateTextField(new JSONStorableString("text", "Vagina target position relative to LabiaTrigger.\n" + "\nX: right from the view of the target." + "\nY: up into vagina." + "\nZ: forward towards belly." ), true); text.height = 390f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _anusOffsetXJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_anusOffsetXJSON); CreateSlider(_anusOffsetXJSON, false); _anusOffsetYJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_anusOffsetYJSON); CreateSlider(_anusOffsetYJSON, false); _anusOffsetZJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_anusOffsetZJSON); CreateSlider(_anusOffsetZJSON, false); text = CreateTextField(new JSONStorableString("text", "Anus target position relative to LabiaTrigger.\n" + "\nX: right from the view of the target." + "\nY: up into vagina." + "\nZ: forward towards belly." ), true); text.height = 390f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _handOffsetXJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_handOffsetXJSON); CreateSlider(_handOffsetXJSON, false); _handOffsetYJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_handOffsetYJSON); CreateSlider(_handOffsetYJSON, false); _handOffsetZJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_handOffsetZJSON); CreateSlider(_handOffsetZJSON, false); text = CreateTextField(new JSONStorableString("text", "Hand target position relative to rHand/lHand rigidbody " + "(origin is near wrists).\n" + "\nX: Towards fingers." + "\nY: Towards back of the hand." + "\nZ: Towards thumb." ), true); text.height = 390f; CreateSpacer(false).height = 20f; CreateSpacer(true).height = 20f; _customTargetOffsetXJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_customTargetOffsetXJSON); CreateSlider(_customTargetOffsetXJSON, false); _customTargetOffsetYJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_customTargetOffsetYJSON); CreateSlider(_customTargetOffsetYJSON, false); _customTargetOffsetZJSON.storeType = JSONStorableParam.StoreType.Full; RegisterFloat(_customTargetOffsetZJSON); CreateSlider(_customTargetOffsetZJSON, false); text = CreateTextField(new JSONStorableString("text", "Offset from custom target.\n" + "\nX: Custom target local x axis." + "\nY: Custom target local y axis." + "\nZ: Custom target local z axis." ), true); text.height = 390f; _penisTarget = null; _targetOffset = new Vector3(0f, 0f, 0f); syncOtherPersonAtoms(); if (containingAtom.type != "Person") { SuperController.LogError($"Please load DivingForeskin on a person, not with a '{containingAtom.type}' atom."); DestroyImmediate(this); return; } _pelvis = containingAtom.rigidbodies.First(rb => rb.name == "pelvis"); _penisBase = containingAtom.freeControllers.First(fc => fc.name == "penisBaseControl"); _penisMid = containingAtom.freeControllers.First(fc => fc.name == "penisMidControl"); _penisTip = containingAtom.freeControllers.First(fc => fc.name == "penisTipControl"); if(_penisBasePositionContolChooser.val == "On" && _penisBase.currentPositionState != FreeControllerV3.PositionState.On) _penisBase.currentPositionState = FreeControllerV3.PositionState.On; if(_penisBasePositionContolChooser.val == "Comply" && _penisBase.currentPositionState != FreeControllerV3.PositionState.Comply) _penisBase.currentPositionState = FreeControllerV3.PositionState.Comply; if(_penisBasePositionContolChooser.val == "Off" && _penisBase.currentPositionState != FreeControllerV3.PositionState.Off) _penisBase.currentPositionState = FreeControllerV3.PositionState.Off; if(_penisBasePositionContolChooser.val == "ParentLink" && _penisBase.currentPositionState != FreeControllerV3.PositionState.ParentLink) _penisBase.currentPositionState = FreeControllerV3.PositionState.ParentLink; if(_penisBasePositionContolChooser.val == "PhysicsLink" && _penisBase.currentPositionState != FreeControllerV3.PositionState.PhysicsLink) _penisBase.currentPositionState = FreeControllerV3.PositionState.PhysicsLink; _penisMid.currentPositionState = FreeControllerV3.PositionState.Off; _penisTip.currentPositionState = FreeControllerV3.PositionState.Off; _gen1 = containingAtom.rigidbodies.First(rb => rb.name == "Gen1"); _gen2 = containingAtom.rigidbodies.First(rb => rb.name == "Gen2"); _gen3 = containingAtom.rigidbodies.First(rb => rb.name == "Gen3"); } catch (Exception e) { SuperController.LogError($"{nameof(DiviningForeskin)}.{nameof(Init)}: {e}"); } } public void OnEnable() { try { _penisTarget = null; syncOtherPersonAtoms(); //SuperController.LogMessage($"{nameof(DiviningForeskin)} enabled"); } catch (Exception e) { SuperController.LogError($"{nameof(DiviningForeskin)}.{nameof(OnEnable)}: {e}"); } } public void OnDisable() { try { if (containingAtom.type == "Person") { _gen1.detectCollisions = true; _gen2.detectCollisions = true; _gen3.detectCollisions = true; //SuperController.LogMessage($"{nameof(DiviningForeskin)} disabled"); } } catch (Exception e) { SuperController.LogError($"{nameof(DiviningForeskin)}.{nameof(OnDisable)}: {e}"); } } public void OnDestroy() { try { if (containingAtom.type == "Person") { _gen1.detectCollisions = true; _gen2.detectCollisions = true; _gen3.detectCollisions = true; //SuperController.LogMessage($"{nameof(DiviningForeskin)} disabled"); } } catch (Exception e) { SuperController.LogError($"{nameof(DiviningForeskin)}.{nameof(OnDestroy)}: {e}"); } } public static bool IsFemale(Atom person) { bool retVal = true; DAZCharacter character = person.GetComponentInChildren(); if (character != null) { retVal = !character.isMale; } return retVal; } public Vector3 GetOffset(string targetType) { Vector3 offset = new Vector3(0f, 0f, 0f); if (targetType == "Mouth") { offset = new Vector3( _mouthOffsetXJSON.val, _mouthOffsetYJSON.val, _mouthOffsetZJSON.val); } else if (targetType == "Vagina") { offset = new Vector3(_vaginaOffsetXJSON.val, _vaginaOffsetYJSON.val, _vaginaOffsetZJSON.val); } else if (targetType == "Anus") { offset = new Vector3(_anusOffsetXJSON.val, _anusOffsetYJSON.val, _anusOffsetZJSON.val); } else if (targetType == "rHand") { offset = new Vector3(_handOffsetXJSON.val, _handOffsetYJSON.val, _handOffsetZJSON.val); } else if (targetType == "lHand") { // swap x axis for left hand offset = new Vector3(-_handOffsetXJSON.val, _handOffsetYJSON.val, _handOffsetZJSON.val); } else if (targetType == "Custom") { offset = new Vector3(_customTargetOffsetXJSON.val, _customTargetOffsetYJSON.val, _customTargetOffsetZJSON.val); } return offset * 0.01f; // cm to m } public bool IsInFront(Component target, Vector3 offset) { // TODO: Configurable Max Angle return Vector3.Dot(target.transform.TransformPoint(offset) - _pelvis.transform.position, _pelvis.transform.forward) > 0.0f; } protected void potentialTargetUpdate(Component potentialTarget, string targetType, Atom PersonAtom, Trigger trigger=null) { Vector3 targetOffset = GetOffset(targetType); float targetDistance = (_penisTip.transform.position - potentialTarget.transform.TransformPoint(targetOffset)).magnitude; bool targetWithinDistance = (targetDistance < _minTargetDistanceJSON.val * 0.01f) || (_minTargetDistanceJSON.val <= 0f); bool targetIsNearer = true; if (_penisTarget != null) { targetIsNearer = targetDistance < (_penisTip.transform.position - _penisTarget.transform.TransformPoint(_targetOffset)).magnitude; } //THIS SETS THE TARGET if (IsInFront(potentialTarget, targetOffset) && targetWithinDistance && targetIsNearer) { //SuperController.LogMessage("potentialTargetUpdate PersonAtom " + PersonAtom + "_penisTarget " + potentialTarget); _penisTarget = potentialTarget; _targetOffset = targetOffset; _targetDistance = targetDistance; _targetType = targetType; _targetTrigger = trigger; _targetAtom = PersonAtom; } } protected void potentialForeskinTargetUpdate(Component potentialTarget, string targetType, Atom PersonAtom, Trigger trigger=null) { Vector3 targetOffset = GetOffset(targetType); float targetDistance = (_penisBase.transform.position - potentialTarget.transform.TransformPoint(targetOffset)).magnitude; bool targetWithinDistance = (targetDistance < _minTargetDistanceJSON.val * 0.01f) || (_minTargetDistanceJSON.val <= 0f); bool targetIsNearer = true; if (_foreskinTarget != null) { targetIsNearer = targetDistance < (_penisMid.transform.position - _foreskinTarget.transform.TransformPoint(_foreskintargetOffset)).magnitude; //SuperController.LogMessage("targetIsNearer " + targetIsNearer); } //THIS SETS FORESKIN TARGET if (IsInFront(potentialTarget, targetOffset) && targetWithinDistance && targetIsNearer) { //SuperController.LogMessage("potentialForeskinTargetUpdate PersonAtom " + PersonAtom + "_foreskinTarget " + potentialTarget); _foreskinTarget = potentialTarget; _foreskintargetOffset = targetOffset; _foreskintargetDistance = targetDistance; _foreskintargetType = targetType; _foreskintargetTrigger = trigger; _foreskintargetAtom = PersonAtom; } } protected void syncInsideState() { if (_penisTarget != null) { _shouldBeInsideTarget = Vector3.Dot(_penisTarget.transform.TransformPoint(_targetOffset) - _penisTip.transform.position, _penisTip.transform.forward) <= 0.0f; _isInsideTarget = _shouldBeInsideTarget; if (_targetTrigger != null) { _isInsideTarget = _isInsideTarget && _targetTrigger.active; } } } protected void syncPenisTarget() { //if (!(_mouthEnabledJSON.val || _vaginaEnabledJSON.val || _anusEnabledJSON.val || _rHandEnabledJSON.val || _lHandEnabledJSON.val) && _customTarget == null) //{ // _penisTarget = null; // return; //} //SuperController.LogMessage("_foreskinTarget " + _foreskinTarget + " _penisTarget " + _penisTarget); try { _targetTipToBaseDistance = (_penisBase.transform.position - _penisTip.transform.position).magnitude; if (_penisTarget != null) { _targetDistance = (_penisTip.transform.position - _penisTarget.transform.TransformPoint(_targetOffset)).magnitude; _targetOffset = GetOffset(_targetType); // need to update offset if changed interactively syncInsideState(); if (!_isInsideTarget) // only consider retargeting outside of target { if (_targetDistance > _minRetargetDistanceJSON.val * 0.01f || !IsInFront(_penisTarget, _targetOffset)) { _penisTarget = null; } } } if (_foreskinTarget != null) { _foreSkinTargetFromBaseDistance = (_penisBase.transform.position - _foreskinTarget.transform.TransformPoint(_foreskintargetOffset)).magnitude; //SuperController.LogMessage("_foreSkinTargetFromBaseDistance " + _foreSkinTargetFromBaseDistance); _foreskintargetDistance = (_penisTip.transform.position - _foreskinTarget.transform.TransformPoint(_foreskintargetOffset)).magnitude; _foreskintargetOffset = GetOffset(_foreskintargetType); // need to update offset if changed interactively if (!_isInsideTarget) // only consider retargeting outside of target { if (_foreskintargetDistance > _minRetargetDistanceJSON.val * 0.01f || !IsInFront(_foreskinTarget, _foreskintargetOffset)) { _foreskinTarget = null; } } } if (_penisTarget == null) { _targetTrigger = null; foreach (OtherPersonInfo personInfo in _otherPersons) { if (_mouthEnabledJSON.val) { potentialTargetUpdate(personInfo.MouthTarget, "Mouth", personInfo.PersonAtom, personInfo.MouthTrigger); } if (_vaginaEnabledJSON.val && personInfo.IsFemale) { potentialTargetUpdate(personInfo.VaginaTarget, "Vagina", personInfo.PersonAtom, personInfo.VaginaTrigger); } if (_anusEnabledJSON.val) { potentialTargetUpdate(personInfo.AnusTarget, "Anus", personInfo.PersonAtom, personInfo.AnusTrigger); } if (_rHandEnabledJSON.val) { potentialTargetUpdate(personInfo.RHandTarget, "rHand", personInfo.PersonAtom, personInfo.RHandTrigger); } if (_lHandEnabledJSON.val) { potentialTargetUpdate(personInfo.LHandTarget, "lHand", personInfo.PersonAtom, personInfo.LHandTrigger); } } if (_customTarget != null) { potentialTargetUpdate(_customTarget, "Custom", _customTargetAtom); } syncInsideState(); } foreach (OtherPersonInfo personInfo in _otherPersons) { potentialForeskinTargetUpdate(personInfo.MouthTarget, "Mouth", personInfo.PersonAtom, personInfo.MouthTrigger); potentialForeskinTargetUpdate(personInfo.VaginaTarget, "Vagina", personInfo.PersonAtom, personInfo.VaginaTrigger); potentialForeskinTargetUpdate(personInfo.AnusTarget, "Anus", personInfo.PersonAtom, personInfo.AnusTrigger); potentialForeskinTargetUpdate(personInfo.RHandTarget, "rHand", personInfo.PersonAtom, personInfo.RHandTrigger); potentialForeskinTargetUpdate(personInfo.LHandTarget, "lHand", personInfo.PersonAtom, personInfo.LHandTrigger); } if (_customTarget != null) { potentialForeskinTargetUpdate(_customTarget, "Custom", _customTargetAtom); } } catch (Exception e) { SuperController.LogError($"{nameof(DiviningForeskin)}.{nameof(syncPenisTarget)}: {e}"); } } // FixedUpdate is called with each physics simulation frame by Unity void FixedUpdate() { try { syncPenisTarget(); // reset detections which might have been turned of by pop out prevention previously _gen1.detectCollisions = true; _gen2.detectCollisions = true; _gen3.detectCollisions = true; if (_adaptationRateJSON.val == 0f) { return; } if(_penisBasePositionContolChooser.val == "On" && _penisBase.currentPositionState != FreeControllerV3.PositionState.On) _penisBase.currentPositionState = FreeControllerV3.PositionState.On; if(_penisBasePositionContolChooser.val == "Comply" && _penisBase.currentPositionState != FreeControllerV3.PositionState.Comply) _penisBase.currentPositionState = FreeControllerV3.PositionState.Comply; if(_penisBasePositionContolChooser.val == "Off" && _penisBase.currentPositionState != FreeControllerV3.PositionState.Off) _penisBase.currentPositionState = FreeControllerV3.PositionState.Off; if(_penisBasePositionContolChooser.val == "ParentLink" && _penisBase.currentPositionState != FreeControllerV3.PositionState.ParentLink) _penisBase.currentPositionState = FreeControllerV3.PositionState.ParentLink; if(_penisBasePositionContolChooser.val == "PhysicsLink" && _penisBase.currentPositionState != FreeControllerV3.PositionState.PhysicsLink) _penisBase.currentPositionState = FreeControllerV3.PositionState.PhysicsLink; //SuperController.LogMessage("_penisTarget " + _penisTarget + " _targetAtom " + _targetAtom); if (_penisBaseRotationContolChooser.val != "Plugin Controlled") { if(_penisBaseRotationContolChooser.val == "On" && _penisBase.currentRotationState != FreeControllerV3.RotationState.On) _penisBase.currentRotationState = FreeControllerV3.RotationState.On; if(_penisBaseRotationContolChooser.val == "Comply" && _penisBase.currentRotationState != FreeControllerV3.RotationState.Comply) _penisBase.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisBaseRotationContolChooser.val == "Off" && _penisBase.currentRotationState != FreeControllerV3.RotationState.Off) _penisBase.currentRotationState = FreeControllerV3.RotationState.Off; if(_penisBaseRotationContolChooser.val == "ParentLink" && _penisBase.currentRotationState != FreeControllerV3.RotationState.ParentLink) _penisBase.currentRotationState = FreeControllerV3.RotationState.ParentLink; if(_penisBaseRotationContolChooser.val == "PhysicsLink" && _penisBase.currentRotationState != FreeControllerV3.RotationState.PhysicsLink) _penisBase.currentRotationState = FreeControllerV3.RotationState.PhysicsLink; if(_penisBaseRotationContolChooser.val == "On" && _penisMid.currentRotationState != FreeControllerV3.RotationState.On) _penisMid.currentRotationState = FreeControllerV3.RotationState.On; if(_penisBaseRotationContolChooser.val == "Comply" && _penisMid.currentRotationState != FreeControllerV3.RotationState.Comply) _penisMid.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisBaseRotationContolChooser.val == "Off" && _penisMid.currentRotationState != FreeControllerV3.RotationState.Off) _penisMid.currentRotationState = FreeControllerV3.RotationState.Off; if(_penisBaseRotationContolChooser.val == "ParentLink" && _penisMid.currentRotationState != FreeControllerV3.RotationState.Comply) _penisMid.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisBaseRotationContolChooser.val == "PhysicsLink" && _penisMid.currentRotationState != FreeControllerV3.RotationState.On) _penisMid.currentRotationState = FreeControllerV3.RotationState.On; if(_penisBaseRotationContolChooser.val == "On" && _penisTip.currentRotationState != FreeControllerV3.RotationState.Comply) _penisTip.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisBaseRotationContolChooser.val == "Comply" && _penisTip.currentRotationState != FreeControllerV3.RotationState.Comply) _penisTip.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisBaseRotationContolChooser.val == "Off" && _penisTip.currentRotationState != FreeControllerV3.RotationState.Off) _penisTip.currentRotationState = FreeControllerV3.RotationState.Off; if(_penisBaseRotationContolChooser.val == "ParentLink" && _penisTip.currentRotationState != FreeControllerV3.RotationState.Comply) _penisTip.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisBaseRotationContolChooser.val == "PhysicsLink" && _penisTip.currentRotationState != FreeControllerV3.RotationState.On) _penisTip.currentRotationState = FreeControllerV3.RotationState.On; } else if (_penisTarget == null) { if(_penisBase.currentRotationState != FreeControllerV3.RotationState.Off) _penisBase.currentRotationState = FreeControllerV3.RotationState.Off; if(_penisMid.currentRotationState != FreeControllerV3.RotationState.Off) _penisMid.currentRotationState = FreeControllerV3.RotationState.Off; if(_penisTip.currentRotationState != FreeControllerV3.RotationState.Off) _penisTip.currentRotationState = FreeControllerV3.RotationState.Off; } else if (_isInsideTarget) { if (!(_targetType.EndsWith("Hand") || _targetType == "Custom")) { if(_penisBase.currentRotationState != FreeControllerV3.RotationState.Comply) _penisBase.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisMid.currentRotationState != FreeControllerV3.RotationState.Off) _penisMid.currentRotationState = FreeControllerV3.RotationState.Off; if(_penisTip.currentRotationState != FreeControllerV3.RotationState.Off) _penisTip.currentRotationState = FreeControllerV3.RotationState.Off; } else { if(_penisBase.currentRotationState != FreeControllerV3.RotationState.Comply) _penisBase.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisMid.currentRotationState != FreeControllerV3.RotationState.Comply) _penisMid.currentRotationState = FreeControllerV3.RotationState.Comply; if(_penisTip.currentRotationState != FreeControllerV3.RotationState.Comply) _penisTip.currentRotationState = FreeControllerV3.RotationState.Comply; } } else { if(_penisBase.currentRotationState != FreeControllerV3.RotationState.On) _penisBase.currentRotationState = FreeControllerV3.RotationState.On; if(_penisMid.currentRotationState != FreeControllerV3.RotationState.On) _penisMid.currentRotationState = FreeControllerV3.RotationState.On; if(_penisTip.currentRotationState != FreeControllerV3.RotationState.Comply) _penisTip.currentRotationState = FreeControllerV3.RotationState.Comply; } if ((_targetAtom != containingAtom || _targetType.EndsWith("Hand")) && _penisTarget != null) { // apply transform to base Quaternion oldRotation = _penisBase.transform.rotation; _penisBase.transform.LookAt(_penisTarget.transform.TransformPoint(_targetOffset), _pelvis.transform.up); // avoid pop out bool popOutDetected = _avoidPopOutJSON.val && ((_verifyWithTriggerJSON.val && _isInsideTarget != _shouldBeInsideTarget) || (!_verifyWithTriggerJSON.val && _shouldBeInsideTarget)) && Quaternion.Angle(_gen1.transform.rotation, _penisBase.transform.rotation) > _popOutDetectionThresholdJSON.val; if (popOutDetected) { //SuperController.LogMessage($"{nameof(DiviningForeskin)}.{nameof(FixedUpdate)}: Attempting to avoid penis pop out."); // will be set back to true in next fixed update _gen1.detectCollisions = false; _gen2.detectCollisions = false; _gen3.detectCollisions = false; } // apply adaptation rate if (!(popOutDetected && _instantPopBackJSON.val)) { float adaptationRate = _adaptationRateJSON.val; if (_dynamicAdaptionRateJSON.val && _minRetargetDistanceJSON.val < _minTargetDistanceJSON.val && !_isInsideTarget) { float minTargetDistance = _minTargetDistanceJSON.val * 0.01f; float minRetargetDistance = _minRetargetDistanceJSON.val * 0.01f; float distanceFac = Mathf.Clamp((_targetDistance - minRetargetDistance) / (minTargetDistance - minRetargetDistance), 0f, 1f); adaptationRate *= 1f - distanceFac*distanceFac; } if (adaptationRate < 100f) { // exponential ease float lerpFac = Mathf.Pow(1f - adaptationRate*0.01f, Time.fixedDeltaTime); _penisBase.transform.rotation = Quaternion.Lerp(_penisBase.transform.rotation, oldRotation, lerpFac); } } // apply transform from base to other penis parts _penisMid.transform.rotation=_penisBase.transform.rotation; _penisTip.transform.rotation=_penisBase.transform.rotation; } // apply tip force if (_penisTarget != null && _tipForceJSON.val > 0 && !_isInsideTarget) { Vector3 forceVec = (_penisTarget.transform.position - _penisTip.transform.position).normalized; // angularFalloff -> smaller force, if tip direction is already aligned with target or even behind it float angularFalloff = 1f - Mathf.Abs(Vector3.Dot(forceVec, _penisTip.transform.forward)); float forceIntesity = _tipForceJSON.val; forceIntesity *= angularFalloff; _gen3.AddForce(forceVec * forceIntesity, ForceMode.VelocityChange); Vector3 torqueVec = Vector3.Cross(_penisTip.transform.forward, forceVec); float torqueIntensity = _tipForceJSON.val; torqueIntensity *= angularFalloff; _gen3.AddTorque(torqueVec * torqueIntensity, ForceMode.Force); } foreach (Atom atom in SuperController.singleton.GetAtoms()) { if (atom.type == "Person") { JSONStorable js = atom.GetStorableByID("geometry"); DAZCharacterSelector dcs = js as DAZCharacterSelector; GenerateDAZMorphsControlUI morphUI = dcs.morphsControlUI; _selector = atom.GetComponentInChildren(); if (atom == containingAtom && _selector.gender == DAZCharacterSelector.Gender.Male) { if (morphUI != null) { foreskinmorphe_choice = morphUI.GetMorphByDisplayName(_foreskinMorpheChooser.val); Frsk_Scale_Point = morphUI.GetMorphByDisplayName("Frsk_Scale_Point"); Frsk_Globe = morphUI.GetMorphByDisplayName("Frsk_Globe"); GI_big = morphUI.GetMorphByDisplayName("Gl_Big"); GI_small = morphUI.GetMorphByDisplayName("Gl_Small"); G2MGensSkinMove = morphUI.GetMorphByDisplayName("G2MGensSkinMove"); PBMFS01 = morphUI.GetMorphByDisplayName("PBMFS01"); Frsk_Globe.morphValue = 0.32f; if(_foreskinPullEnabledJSON.val) { if (_foreskinTarget != null) { if ( _foreskintargetType == "Vagina" || _foreskintargetType == "Anus") { A=_targetTipToBaseDistance + _foreskinPullVagAnusFromBaseOffsetJSON.val - _foreskinPullMinAmountJSON.val; B=_targetTipToBaseDistance + _foreskinPullVagAnusFromBaseOffsetJSON.val + _foreskinPullMaxAmountJSON.val; } else if ( _foreskintargetType == "Mouth" ) { A=_targetTipToBaseDistance + _foreskinPullMouthFromBaseOffsetJSON.val - _foreskinPullMinAmountJSON.val; B=_targetTipToBaseDistance + _foreskinPullMouthFromBaseOffsetJSON.val + _foreskinPullMaxAmountJSON.val; } else { A = _targetTipToBaseDistance + _foreskinPullHandFromBaseOffsetJSON.val - _foreskinPullMinAmountJSON.val; B = _targetTipToBaseDistance + _foreskinPullHandFromBaseOffsetJSON.val + _foreskinPullMaxAmountJSON.val; } //Rescale foreskin amounts to between 0 and 1 //A=0.11f; //B=0.15f; C=0f; D=1f; foreskin_amount = (_foreSkinTargetFromBaseDistance-A)/(B-A) * (D-C) + C; foreskin_amount_negative = (_foreSkinTargetFromBaseDistance-A)/(B-A) * (-1-C) + C; foreskin_amount_2x = (_foreSkinTargetFromBaseDistance-A)/(B-A) * (D*2-C) + C; foreskin_G2MGensSkinMove_amount = (_foreSkinTargetFromBaseDistance-A)/(B-A) * (-0.4f-_G2MGensSkinMoveAmountJSON.val) + _G2MGensSkinMoveAmountJSON.val; foreach(String foreskinMorpheChoice in _foreskinMorpheChoices) { foreskinmorphe_choice = morphUI.GetMorphByDisplayName(foreskinMorpheChoice); if (foreskinMorpheChoice == _foreskinMorpheChooser.val) { foreskinmorphe_choice.morphValue = Mathf.Clamp(foreskin_amount, 0f, 1f); } else { foreskinmorphe_choice.morphValue = 0f; } } Frsk_Scale_Point.morphValue = Mathf.Clamp(foreskin_amount, 0f, 1f); GI_big.morphValue = Mathf.Clamp(foreskin_amount_negative, -1f, 0f); GI_small.morphValue = Mathf.Clamp(foreskin_amount_2x, 0f, 2f); PBMFS01.morphValue = Mathf.Clamp(foreskin_amount, 0f, 1f); G2MGensSkinMove.morphValue = Mathf.Clamp(foreskin_G2MGensSkinMove_amount, -0.4f, _G2MGensSkinMoveAmountJSON.val); } else { foreach(String foreskinMorpheChoice in _foreskinMorpheChoices) { foreskinmorphe_choice = morphUI.GetMorphByDisplayName(foreskinMorpheChoice); if (foreskinMorpheChoice == _foreskinMorpheChooser.val) { foreskinmorphe_choice.morphValue = 1f; } else { foreskinmorphe_choice.morphValue = 0f; } } Frsk_Scale_Point.morphValue = 0.0f; GI_big.morphValue = -1.0f; GI_small.morphValue = 2.0f; PBMFS01.morphValue = 1; G2MGensSkinMove.morphValue = 0f; } } else { foreach(String foreskinMorpheChoice in _foreskinMorpheChoices) { foreskinmorphe_choice = morphUI.GetMorphByDisplayName(foreskinMorpheChoice); foreskinmorphe_choice.morphValue = 0f; } Frsk_Scale_Point.morphValue = 1.0f; GI_big.morphValue = 0.0f; GI_small.morphValue = 0f; PBMFS01.morphValue = 0f; G2MGensSkinMove.morphValue = 0f; } } } } } } catch (Exception e) { SuperController.LogError($"{nameof(DiviningForeskin)}.{nameof(FixedUpdate)}: {e}"); } } } } // namespace ToumeiHitsuji end