using UnityEngine; using UnityEngine.UI; using System; using System.Collections; using System.Collections.Generic; using SimpleJSON; using System.Linq; using System.IO; using MeshVR; using Request = MeshVR.AssetLoader.AssetBundleFromFileRequest; using AssetBundles; using MVR; // PersonFluidEditor v1.0 // // Allows to add genital fluids for a character (and edit it's appearance and behavior) namespace PersonFluidEditorPlugin { public class PersonFluidEditor : MVRScript { public JSONStorableFloat FluidMaleOffsetY; public JSONStorableFloat FluidMaleOffsetZ; public JSONStorableFloat FluidMaleRotationX; public JSONStorableFloat FluidFemaleOffsetY; public JSONStorableFloat FluidFemaleOffsetZ; public JSONStorableFloat FluidFemaleRotationX; public JSONStorableStringChooser FluidAppareance; protected List appearanceChoices; public JSONStorableColor FluidMainColor; public JSONStorableFloat FluidMainOpacity; public JSONStorableBool FluidEnableShadows; public JSONStorableStringChooser FluidStreamType; protected List streamTypeChoices; public JSONStorableFloat StreamAmount; public JSONStorableFloat StreamSpeed; public JSONStorableFloat StreamForce; public JSONStorableFloat StreamAngle; public JSONStorableFloat StreamThickness; public JSONStorableFloat FluidThickness; public JSONStorableFloat FluidStickyness; public JSONStorableFloat FluidLifetimeMin; public JSONStorableFloat FluidLifetimeMax; public JSONStorableBool FluidAltMotionMode; public JSONStorableBool IsFutanari; private UIDynamicButton btnTestFluidStart; private UIDynamicButton btnTestFluidStop; private GameObject FluidInstance; private Transform ParticleRoot; private Transform ParticleMotionRoot; private ParticleSystem prtComponent; private ParticleSystemRenderer prtRendererComponent; private ParticleSystem prtDeathComponent; private ParticleSystemRenderer prtDeathRendererComponent; // Others private bool isSprayActive = false; // Gender DAZCharacterSelector myGeometry; DAZCharacterSelector.Gender myGender = DAZCharacterSelector.Gender.None; private bool isMale = false; private bool isFuta = false; // Curves private AnimationCurve cuGravPumping; private AnimationCurve cuGravContinuous; // Original values private float initialCollisionScale = 0f; // Actions public JSONStorableFloat A_SprayMultipleTimes; public JSONStorableString A_SprayMultipleTimesCustom; public JSONStorableAction A_StartStream; public JSONStorableAction A_StopStream; public JSONStorableColor A_ChangeFluidMainColor; public JSONStorableFloat A_ChangeFluidMainOpacity; public JSONStorableFloat A_ChangeStreamAmount; public JSONStorableFloat A_ChangeStreamSpeed; public JSONStorableFloat A_ChangeStreamForce; public JSONStorableFloat A_ChangeStreamAngle; public JSONStorableFloat A_ChangeStreamThickness; public JSONStorableFloat A_ChangeFluidThickness; public JSONStorableFloat A_ChangeFluidStickyness; public JSONStorableAction A_ResetAppearanceToDefault; public JSONStorableFloat A_ChangeFluidState; // Paths public static string PLUGIN_PATH; public static string FLUIDS_BUNDLE_PATH = "/fluids101person_assets.vfxbundle"; public static string MALE_PREFAB_PATH = "Assets/VAMParticles/Fluids101Person/hzm_fluid_male.prefab"; public static string FEMALE_PREFAB_PATH = "Assets/VAMParticles/Fluids101Person/hzm_fluid_female.prefab"; // UI private UIDynamic[] maleUIElements; private UIDynamic[] femaleUIElements; // Assetbundle private Request vfxBundleRequest = null; // Timer & security private float internalTimer = 0f; private float genderCheckPeriod = 2f; private float genderNextCheck = 0f; private bool fluidSystemReady = false; public override void Init() { try { if (containingAtom.type != "Person") { SuperController.LogError("Please add PersonFluidEditor on an Person Atom."); return; } PLUGIN_PATH = GetPluginPath(this); maleUIElements = new UIDynamic[4]; femaleUIElements = new UIDynamic[4]; UIDynamicSlider tmpSlider; UIDynamicToggle tmpToggle; // Type of appearance (lighting) appearanceChoices = new List(); appearanceChoices.Add("Unlit"); appearanceChoices.Add("Lit"); // Type of streams (lighting) streamTypeChoices = new List(); streamTypeChoices.Add("Pumping"); streamTypeChoices.Add("Continuous"); FluidMaleOffsetZ = new JSONStorableFloat("Fluid Forward offset (male)", 0.038f, -0.2f, 0.2f); FluidMaleOffsetZ.setCallbackFunction += (val) => { FluidPositionCallback(); }; tmpSlider = CreateSlider(FluidMaleOffsetZ, false); tmpSlider.valueFormat = "F3"; maleUIElements[0] = (UIDynamic)tmpSlider; FluidMaleOffsetY = new JSONStorableFloat("Fluid Up/Down offset (male)", 0f, -0.2f, 0.2f); FluidMaleOffsetY.setCallbackFunction += (val) => { FluidPositionCallback(); }; tmpSlider = CreateSlider(FluidMaleOffsetY, false); tmpSlider.valueFormat = "F3"; maleUIElements[1] = (UIDynamic)tmpSlider; FluidMaleRotationX = new JSONStorableFloat("Fluid Rotation (male)", 0f, -45f, 90f); FluidMaleRotationX.setCallbackFunction += (val) => { FluidPositionCallback(); }; tmpSlider = CreateSlider(FluidMaleRotationX, false); tmpSlider.valueFormat = "F3"; maleUIElements[2] = (UIDynamic)tmpSlider; // Duplicated these because of the default values (handling runtime default values with the same JSONStorable is a pain in the ass) FluidFemaleOffsetZ = new JSONStorableFloat("Fluid Forward offset (female)", 0.023f, -0.2f, 0.2f); FluidFemaleOffsetZ.setCallbackFunction += (val) => { FluidPositionCallback(); }; tmpSlider = CreateSlider(FluidFemaleOffsetZ, false); tmpSlider.valueFormat = "F3"; femaleUIElements[0] = (UIDynamic)tmpSlider; FluidFemaleOffsetY = new JSONStorableFloat("Fluid Up/Down offset (female)", -0.019f, -0.2f, 0.2f); FluidFemaleOffsetY.setCallbackFunction += (val) => { FluidPositionCallback(); }; tmpSlider = CreateSlider(FluidFemaleOffsetY, false); tmpSlider.valueFormat = "F3"; femaleUIElements[1] = (UIDynamic)tmpSlider; FluidFemaleRotationX = new JSONStorableFloat("Fluid Rotation (female)", 60f, 0, 120f); FluidFemaleRotationX.setCallbackFunction += (val) => { FluidPositionCallback(); }; tmpSlider = CreateSlider(FluidFemaleRotationX, false); tmpSlider.valueFormat = "F3"; femaleUIElements[2] = (UIDynamic)tmpSlider; FluidAppareance = new JSONStorableStringChooser("Fluid appearance", appearanceChoices, "Unlit", "Fluid appearance", FluidAppareanceCallback); UIDynamicPopup FAdp = CreatePopup(FluidAppareance, false); FAdp.labelWidth = 300f; HSVColor hsvc = HSVColorPicker.RGBToHSV(1f, 0.9482704f, 0.7783019f); FluidMainColor = new JSONStorableColor("Fluid color", hsvc, MainColorCallback); CreateColorPicker(FluidMainColor); FluidMainOpacity = new JSONStorableFloat("Fluid opacity", 0.15f, MainOpacityCallback, 0f, 1f); CreateSlider(FluidMainOpacity, false); FluidEnableShadows = new JSONStorableBool("Enable fluid shadows", false, FluidEnableShadowsCallback); FluidEnableShadows.storeType = JSONStorableParam.StoreType.Full; CreateToggle(FluidEnableShadows, false); IsFutanari = new JSONStorableBool("Penis Cum", false, IsFutanariCallback); IsFutanari.storeType = JSONStorableParam.StoreType.Full; CreateToggle(IsFutanari, false); FluidStreamType = new JSONStorableStringChooser("Stream type", streamTypeChoices, "Pumping", "Stream type", FluidStreamTypeCallback); UIDynamicPopup FSTdp = CreatePopup(FluidStreamType, false); FSTdp.labelWidth = 200f; StreamAmount = new JSONStorableFloat("Stream amount", 150f, StreamAmountCallback, 10f, 1000f); CreateSlider(StreamAmount, false); StreamSpeed = new JSONStorableFloat("Stream speed", 0.8f, StreamSpeedCallback, 0.1f, 1f); CreateSlider(StreamSpeed, false); StreamForce = new JSONStorableFloat("Stream force", 3.0f, StreamForceCallback, 0f, 6f); CreateSlider(StreamForce, false); StreamAngle = new JSONStorableFloat("Stream angle", 4.0f, StreamAngleCallback, 0.5f, 45f); tmpSlider = CreateSlider(StreamAngle, false); femaleUIElements[3] = (UIDynamic)tmpSlider; StreamThickness = new JSONStorableFloat("Stream thickness", 1.0f, StreamThicknessCallback, 0.2f, 5f); CreateSlider(StreamThickness, false); FluidThickness = new JSONStorableFloat("Fluid thickness", 1.0f, FluidThicknessCallback, 0.2f, 5f); CreateSlider(FluidThickness, false); FluidStickyness = new JSONStorableFloat("Fluid stickiness", 0.8f, FluidStickynessCallback, 0.2f, 0.99f); CreateSlider(FluidStickyness, false); FluidLifetimeMin = new JSONStorableFloat("Fluid lifetime (min)", 8f, FluidLifetimeMinCallback, 0.5f, 15f); CreateSlider(FluidLifetimeMin, false); FluidLifetimeMax = new JSONStorableFloat("Fluid lifetime (max)", 11f, FluidLifetimeMaxCallback, 0.5f, 20f); CreateSlider(FluidLifetimeMax, false); FluidAltMotionMode = new JSONStorableBool("Alternative motion mode", false, FluidAltMotionModeCallback); FluidAltMotionMode.storeType = JSONStorableParam.StoreType.Full; tmpToggle = CreateToggle(FluidAltMotionMode, false); maleUIElements[3] = (UIDynamic)tmpToggle; JSONStorableString helpText = new JSONStorableString("Help", "Help - Person fluids\n\n" + "" + "IMPORTANT: If you switch particles in the Asset tab of the CUA, don't forget to reload the script to enable customization.\n\n" + "NOTES: This is an expensive physical particle system. Default parameters are meant to run properly. If you use continuous streams, performance can be impacted.\n\n" + "Fluid appearance is the way fluid looks depending on the light.\n" + "- Unlit ignores the light which means the fluid will be visible in complete darkness.\n" + "- Lit takes in account the lighting, which means the fluid will be invisible in complete darkness but it will also be coloured by the lights around.\n\n" + "Fluid shadows adds shadows to the stream and fluid. Might be expensive on some low end configurations.\n\n" + "Alternative motion mode (only for male characters) Changes the motion mode of the particles. Enabling this will modify the way the emitter spawn the particles and result in an unrealistic behavior of the particles if the emitter direction, force or speed is changed abruptly. It might be usefull to create a different visual behavior. If you don't understand : do not enable the option.\n\n" + "FUNCTIONS\nYou have several function available to change the appearance during the scene.\n\n" + "All these functions starts with Change xxxx.\n\nThey will allow you to change the appearance of the fluid during the scene without changing the default setup of your fluid.\n\n" + "There is also a function allowing you to reset the appearance to the default one configured in the plugin called Reset settings to default\n\n" + "TRIGGERS\nYou have four triggers available\n\n" + "- Start stream\n" + " Start shooting and never stops.\n\n" + "- Stop stream\n" + " Stop shooting.\n\n" + "- Spray X times, random duration\n" + " Select a value and it will shoot as many times as configured with a tiny random delay between each shot.\n\n" + "- Spray X times, customizable\n" + " Type in a text sequence to customize the behavior. The sequence is written in milliseconds each sequence is composed of a duration and a pause and each sequence is separated by a semicolon, like this :\n\n" + " 0.2,0.1;0.5,0.1\n\n" + "Which translates to: 2 shots, the first has a duration of 0.2sec and a pause of 0.1sec, the second one as a duration of 0.5sec and a pause of 0.1sec\n\n" + "No special characters, no white space, only numbers, comma and semicolon. You can add as much sequences as you want." + "" ); UIDynamic helpTextfield = CreateTextField(helpText, true); helpTextfield.height = 1080.0f; btnTestFluidStart = CreateButton("Test fluid - Start", true); if (btnTestFluidStart != null) { btnTestFluidStart.button.onClick.AddListener( () => { StartStream(); } ); } btnTestFluidStop = CreateButton("Test fluid - Stop", true); if (btnTestFluidStop != null) { btnTestFluidStop.button.onClick.AddListener( () => { StopStream(); } ); } RegisterFloat(FluidMaleOffsetY); RegisterFloat(FluidMaleOffsetZ); RegisterFloat(FluidMaleRotationX); RegisterFloat(FluidFemaleOffsetY); RegisterFloat(FluidFemaleOffsetZ); RegisterFloat(FluidFemaleRotationX); RegisterStringChooser(FluidAppareance); RegisterColor(FluidMainColor); RegisterFloat(FluidMainOpacity); RegisterBool(FluidEnableShadows); RegisterStringChooser(FluidStreamType); RegisterFloat(StreamAmount); RegisterFloat(StreamSpeed); RegisterFloat(StreamForce); RegisterFloat(StreamAngle); RegisterFloat(StreamThickness); RegisterFloat(FluidThickness); RegisterFloat(FluidStickyness); RegisterFloat(FluidLifetimeMin); RegisterFloat(FluidLifetimeMax); RegisterBool(FluidAltMotionMode); // ************************************************ // ** ACTIONS // ************************************************ A_SprayMultipleTimes = new JSONStorableFloat("Spray X times, random duration", 0f, A_SprayMultipleTimesCallback, 0f, 20f){ isStorable = false }; A_SprayMultipleTimesCustom = new JSONStorableString("Spray X times, customizable", "", A_SprayMultipleTimesCustomCallback){ isStorable = false }; A_StartStream = new JSONStorableAction("Start stream", () => { StartStream(); }); A_StopStream = new JSONStorableAction("Stop stream", () => { StopStream(); }); HSVColor hsvc2 = HSVColorPicker.RGBToHSV(1f, 0.9482704f, 0.7783019f); A_ChangeFluidMainColor = new JSONStorableColor("Change fluid color", hsvc2, A_MainColorCallback){ isStorable = false }; A_ChangeFluidMainOpacity = new JSONStorableFloat("Change fluid opacity", 0f, A_MainOpacityCallback, 0f, 1f){ isStorable = false }; A_ChangeStreamAmount = new JSONStorableFloat("Change stream amount", 0f, StreamAmountCallback, 10f, 1000f){ isStorable = false }; A_ChangeStreamSpeed = new JSONStorableFloat("Change stream speed", 0f, StreamSpeedCallback, 0.1f, 1f){ isStorable = false }; A_ChangeStreamForce = new JSONStorableFloat("Change stream force", 0f, StreamForceCallback, 0f, 6f){ isStorable = false }; A_ChangeStreamAngle = new JSONStorableFloat("Change stream angle", 0f, StreamAngleCallback, 0.5f, 45f){ isStorable = false }; A_ChangeStreamThickness = new JSONStorableFloat("Change stream thickness", 0f, StreamThicknessCallback, 0.2f, 5f){ isStorable = false }; A_ChangeFluidThickness = new JSONStorableFloat("Change fluid thickness", 0f, FluidThicknessCallback, 0.2f, 5f){ isStorable = false }; A_ChangeFluidStickyness = new JSONStorableFloat("Change fluid stickyness", 0f, FluidStickynessCallback, 0.2f, 0.99f){ isStorable = false }; A_ChangeFluidState = new JSONStorableFloat("Change fluid state", 0f, 0f, 0.1f){ isStorable = false, isRestorable = false }; A_ChangeFluidState.setCallbackFunction += (val) => { ToggleStream(); }; A_ResetAppearanceToDefault = new JSONStorableAction("Reset settings to default", () => { UpdateParticleSettings(); }); RegisterFloat(A_SprayMultipleTimes); RegisterString(A_SprayMultipleTimesCustom); RegisterAction(A_StartStream); RegisterAction(A_StopStream); RegisterColor(A_ChangeFluidMainColor); RegisterFloat(A_ChangeFluidMainOpacity); RegisterFloat(A_ChangeStreamAmount); RegisterFloat(A_ChangeStreamSpeed); RegisterFloat(A_ChangeStreamForce); RegisterFloat(A_ChangeStreamAngle); RegisterFloat(A_ChangeStreamThickness); RegisterFloat(A_ChangeFluidThickness); RegisterFloat(A_ChangeFluidStickyness); RegisterFloat(A_ChangeFluidState); RegisterAction(A_ResetAppearanceToDefault); // Initializing curves initFluidCurves(); // Grabbing geometry myGeometry = containingAtom.GetStorableByID("geometry") as DAZCharacterSelector; // Loading the assetbundle containing the fluids Request request = new AssetLoader.AssetBundleFromFileRequest {path = PLUGIN_PATH + FLUIDS_BUNDLE_PATH, callback = OnBundleLoaded}; AssetLoader.QueueLoadAssetBundleFromFile(request); } catch(Exception e) { SuperController.LogError("Person - Fluid Editor - Exception caught: " + e); } } private void OnBundleLoaded(Request aRequest) { try { vfxBundleRequest = aRequest; ReadGender(); InitMain(); } catch(Exception e) { SuperController.LogError("Person - Fluid Editor - Exception caught: " + e); } } void Start() { } void Update() { // The update system checking for gender changes is not active until everything is loaded if (fluidSystemReady == false) return; internalTimer += Time.deltaTime; if (internalTimer >= genderNextCheck) { genderNextCheck = internalTimer + genderCheckPeriod; checkGender(); } } private void ReadGender() { myGender = myGeometry != null ? myGeometry.gender : DAZCharacterSelector.Gender.None; isMale = myGender == DAZCharacterSelector.Gender.Male ? true : false; } private void GenderMale() { myGender = DAZCharacterSelector.Gender.Male; isMale = true; } private void GenderFemale() { myGender = DAZCharacterSelector.Gender.Female; isMale = false; } private void InitMain() { GameObject FluidGO; Rigidbody tmpRB; if (isMale) { FluidGO = vfxBundleRequest.assetBundle.LoadAsset(MALE_PREFAB_PATH); tmpRB = containingAtom.rigidbodies.First(rb => rb.name == "Gen3"); } else { FluidGO = vfxBundleRequest.assetBundle.LoadAsset(FEMALE_PREFAB_PATH); tmpRB = containingAtom.rigidbodies.First(rb => rb.name == "LabiaTrigger"); } FluidInstance = Instantiate(FluidGO); FluidInstance.transform.SetParent(tmpRB.transform, false); ParticleRoot = FluidInstance.transform; if (isMale) { InitMotionMode(); } else { InitParticle(ParticleRoot); } fluidSystemReady = true; FluidPositionCallback(); UpdateUIState(); } private void InitMotionMode() { // Getting motion mode child foreach( Transform motionModechild in ParticleRoot ) { ParticleMotionRoot = motionModechild; ParticleMotionRoot.gameObject.SetActive(true); InitParticle( ParticleMotionRoot ); break; } } private void InitParticle(Transform ParticleMotionRoot) { foreach( Transform child in ParticleMotionRoot ) { child.gameObject.SetActive(true); prtComponent = child.GetComponent(); if( prtComponent ) { prtRendererComponent = child.GetComponent(); foreach( Transform subChild in child ) { // Looking for first child with this name if( subChild.name == "fluid_on_death" ) { prtDeathComponent = subChild.GetComponent(); if( prtDeathComponent ) { prtDeathRendererComponent = subChild.GetComponent(); var prtDeathCollision = prtDeathComponent.collision; initialCollisionScale = prtDeathCollision.radiusScale; } } } UpdateParticleSettings(); // Sets all the parameters of the particle ChangeFluidChildParticle( FluidAppareance.val, FluidAltMotionMode.val ); // Change the particle appearance } else { logError("Person - Fluid Editor : your CUA is incorrect and cannot be used with this script."); } } } private void checkGender () { // If we're still not initialized if (fluidSystemReady == false || myGender == null || myGeometry == null) return; // If the gender has changed, we reset the UI and objects if (myGender != myGeometry.gender && !isFuta) { CleanAssets(); ReadGender(); InitMain(); } } private void UpdateParticleSettings() { MainColorCallback( FluidMainColor ); MainOpacityCallback( FluidMainOpacity.val ); FluidEnableShadowsCallback( FluidEnableShadows.val ); FluidStreamTypeCallback( FluidStreamType.val ); StreamAmountCallback( StreamAmount.val ); StreamSpeedCallback( StreamSpeed.val ); StreamForceCallback( StreamForce.val ); StreamAngleCallback( StreamAngle.val ); StreamThicknessCallback( StreamThickness.val ); FluidThicknessCallback( FluidThickness.val ); FluidStickynessCallback ( FluidStickyness.val ); FluidLifetimeMinCallback( FluidLifetimeMin.val ); } private void UpdateUIState() { Vector3 maleVecVal = new Vector3(1,1,1); Vector3 femaleVecVal = new Vector3(0,0,0); bool maleLayoutVal = false; bool femaleLayoutVal = true; if (!isMale) { maleVecVal = new Vector3(0,0,0); femaleVecVal = new Vector3(1,1,1); maleLayoutVal = true; femaleLayoutVal = false; } foreach (UIDynamic uidElem in maleUIElements) { uidElem.GetComponent().transform.localScale = maleVecVal; uidElem.GetComponent().ignoreLayout = maleLayoutVal; } foreach (UIDynamic uidElem in femaleUIElements) { uidElem.GetComponent().transform.localScale = femaleVecVal; uidElem.GetComponent().ignoreLayout = femaleLayoutVal; } } protected void ChangeFluidChildParticle( string particleName, bool altMotionModeEnabled ) { if( ParticleRoot != null ) { Transform targetRoot = ParticleRoot; // Getting motion mode child if (isMale) { foreach( Transform motionModechild in ParticleRoot ) { motionModechild.gameObject.SetActive(false); if( motionModechild.name == "native_mode" && altMotionModeEnabled == false ) { ParticleMotionRoot = motionModechild; ParticleMotionRoot.gameObject.SetActive(true); } else if ( motionModechild.name == "alt_mode" && altMotionModeEnabled == true ) { ParticleMotionRoot = motionModechild; ParticleMotionRoot.gameObject.SetActive(true); } targetRoot = ParticleMotionRoot; } } int loopCount = 0; foreach( Transform child in targetRoot ) { child.gameObject.SetActive(false); if( loopCount == appearanceChoices.IndexOf(particleName) ) { child.gameObject.SetActive(true); prtComponent = child.GetComponent(); if( prtComponent ) { prtRendererComponent = child.GetComponent(); foreach( Transform subChild in child ) { if( subChild.name == "fluid_on_death" ) { prtDeathComponent = subChild.GetComponent(); if( prtDeathComponent ) { prtDeathRendererComponent = subChild.GetComponent(); var prtDeathCollision = prtDeathComponent.collision; initialCollisionScale = prtDeathCollision.radiusScale; } } } } } loopCount++; } // Setting parameters UpdateParticleSettings(); } } protected void StartStream() { if( prtComponent != null ) prtComponent.Play(); } protected void StopStream() { if (prtComponent != null) { prtComponent.Stop(); } } protected void ToggleStream() { if (!fluidSystemReady) return; if (A_ChangeFluidState.val > 0f) { StartStream(); } else { StopStream(); } } // ************************** // Callbacks & similar // ************************** protected void FluidPositionCallback() { if (!fluidSystemReady) return; if (isMale) { ParticleRoot.localPosition = new Vector3(0f, FluidMaleOffsetY.val, FluidMaleOffsetZ.val); FluidInstance.transform.localRotation = Quaternion.Euler(FluidMaleRotationX.val, 0f, 0f); } else { ParticleRoot.localPosition = new Vector3(0f, FluidFemaleOffsetY.val, FluidFemaleOffsetZ.val); FluidInstance.transform.localRotation = Quaternion.Euler(FluidFemaleRotationX.val, 0f, 0f); } } protected void FluidAppareanceCallback(string appearanceChoice) { ChangeFluidChildParticle( appearanceChoice, FluidAltMotionMode.val ); } protected void MainColorCallback(JSONStorableColor color) { if( prtComponent ) { Color newColor = new Color(color.colorPicker.currentColor.r, color.colorPicker.currentColor.g, color.colorPicker.currentColor.b, FluidMainOpacity.val); var prtMain = prtComponent.main; prtMain.startColor = newColor; } } protected void MainOpacityCallback(float opacity) { if( prtComponent ) { var prtMain = prtComponent.main; prtMain.startColor = new Color(FluidMainColor.colorPicker.currentColor.r, FluidMainColor.colorPicker.currentColor.g, FluidMainColor.colorPicker.currentColor.b, opacity); } } protected void FluidEnableShadowsCallback(bool enabled) { if( prtRendererComponent ) { prtRendererComponent.shadowCastingMode = enabled == true ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.Off; prtDeathRendererComponent.shadowCastingMode = enabled == true ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.Off; } } protected void IsFutanariCallback(bool futa) { isMale = futa; isFuta = futa; CleanAssets(); if (futa) { GenderMale(); } else { GenderFemale(); } InitMain(); } protected void FluidStreamTypeCallback(string streamTypeChoice) { if( prtComponent ) { var prtMain = prtComponent.main; if( streamTypeChoice == "Continuous" ) { prtMain.gravityModifier = new ParticleSystem.MinMaxCurve(1.0f, cuGravContinuous, cuGravContinuous); } else { prtMain.gravityModifier = new ParticleSystem.MinMaxCurve(1.0f, cuGravPumping, cuGravPumping); } } } protected void StreamAmountCallback(float amount) { if( prtComponent ) { var prtEmission = prtComponent.emission; prtEmission.rateOverTime = amount; } } protected void StreamSpeedCallback(float speed) { if( prtComponent ) { var prtMain = prtComponent.main; prtMain.simulationSpeed = speed; } } protected void StreamForceCallback(float force) { if( prtComponent ) { if (isMale) { if( ParticleMotionRoot.name == "native_mode" ) { var prtMain = prtComponent.main; prtMain.startSpeed = force; } else { var prtVOL = prtComponent.velocityOverLifetime; prtVOL.yMultiplier = force; } } else { var prtMain = prtComponent.main; prtMain.startSpeed = force; } } } protected void StreamAngleCallback(float angle) { if (isMale) return; if( prtComponent ) { var prtShape = prtComponent.shape; prtShape.angle = angle; } } protected void StreamThicknessCallback(float thickness) { if( prtComponent ) { var prtMain = prtComponent.main; prtMain.startSize = thickness; } } protected void FluidThicknessCallback(float thickness) { if( prtComponent ) { var prtDeathMain = prtDeathComponent.main; prtDeathMain.startSize = thickness; var prtDeathCollision = prtDeathComponent.collision; prtDeathCollision.radiusScale = initialCollisionScale * thickness; } } protected void FluidStickynessCallback(float stickyness) { if( prtComponent ) { var prtDeathCollision = prtDeathComponent.collision; prtDeathCollision.dampen = stickyness; } } protected void FluidLifetimeMinCallback(float lifetimeMin) { if( prtComponent ) { var prtDeathMain = prtDeathComponent.main; prtDeathMain.startLifetime = new ParticleSystem.MinMaxCurve(lifetimeMin, FluidLifetimeMax.val); } } protected void FluidLifetimeMaxCallback(float lifetimeMax) { if( prtComponent ) { var prtDeathMain = prtDeathComponent.main; prtDeathMain.startLifetime = new ParticleSystem.MinMaxCurve(FluidLifetimeMin.val, lifetimeMax); } } protected void FluidAltMotionModeCallback(bool enabled) { ChangeFluidChildParticle( FluidAppareance.val, enabled ); } private void A_SprayMultipleTimesCallback(float numtimes) { DoSprayMultipleTimesRandomDuration( (int)Mathf.Floor(numtimes) ); A_SprayMultipleTimes.val = 0f; } private void A_SprayMultipleTimesCustomCallback(string customText) { DoSprayMultipleTimesCustom( customText ); A_SprayMultipleTimesCustom.val = ""; } protected void A_MainColorCallback(JSONStorableColor color) { if( prtComponent ) { var selectedColor = HSVColorPicker.HSVToRGB(color.val); var prtMain = prtComponent.main; Color newColor = new Color(selectedColor.r, selectedColor.g, selectedColor.b, prtMain.startColor.color.a); prtMain.startColor = newColor; } } protected void A_MainOpacityCallback(float opacity) { if( prtComponent ) { var prtMain = prtComponent.main; prtMain.startColor = new Color(prtMain.startColor.color.r, prtMain.startColor.color.g, prtMain.startColor.color.b, opacity); } } private void DoSprayMultipleTimesRandomDuration(int numtimes) { if( isSprayActive == false ) { StartCoroutine(SprayMultipleTimesRandomDuration(numtimes)); } } private void DoSprayMultipleTimesCustom(string customText) { if( isSprayActive == false ) { StartCoroutine(SprayMultipleTimesCustom(customText)); } } // ************************** // COROUTINES // ************************** private IEnumerator SprayMultipleTimesRandomDuration(int numtimes) { // Spraying, preventing another to start isSprayActive = true; int sprayLoops = 0; while (sprayLoops < numtimes) { prtComponent.Play(); float sprayTime = UnityEngine.Random.Range(0.1f,1f); yield return new WaitForSeconds(sprayTime); prtComponent.Stop(); float pauseTime = UnityEngine.Random.Range(0.2f,0.4f); yield return new WaitForSeconds(pauseTime); sprayLoops++; } // Spray done, we can trigger another one isSprayActive = false; } private IEnumerator SprayMultipleTimesCustom(string customText) { // Spraying, preventing another to start isSprayActive = true; // Splitting my custom text string[] seq = customText.Split(';'); // Doing the loop ! if( seq.Length > 0 ) { foreach (string seqElement in seq) { string[] sprayElem = seqElement.Split(','); if( sprayElem.Length == 2 ) { prtComponent.Play(); yield return new WaitForSeconds(float.Parse(sprayElem[0])); prtComponent.Stop(); yield return new WaitForSeconds(float.Parse(sprayElem[1])); } } } // Spray done, we can trigger another one isSprayActive = false; } // ************************** // Local Tools // ************************** private void logDebug( string debugText ) { SuperController.LogMessage( debugText ); } private void logError( string debugText ) { SuperController.LogError( debugText ); } // *********************************************************** // CURVES // *********************************************************** private void initFluidCurves() { // ******** GRAVITY Keyframe[] ks = new Keyframe[2]; ks[0] = new Keyframe(0f, 1f); ks[0].inTangent = 0f; ks[0].inWeight = 0.3333333f; ks[0].outTangent = 0f; ks[0].outWeight = 0.04462659f; ks[0].weightedMode = WeightedMode.None; ks[1] = new Keyframe(1f, 2f); ks[1].inTangent = 0f; ks[1].inWeight = 0.3333333f; ks[1].outTangent = 0f; ks[1].outWeight = 0.3333333f; ks[1].weightedMode = WeightedMode.None; cuGravPumping = new AnimationCurve(ks); ks = new Keyframe[2]; ks[0] = new Keyframe(0f, 1f); ks[0].inTangent = 0f; ks[0].inWeight = 0.3333333f; ks[0].outTangent = 0f; ks[0].outWeight = 0.3333333f; ks[0].weightedMode = WeightedMode.None; ks[1] = new Keyframe(1f, 1f); ks[1].inTangent = 0f; ks[1].inWeight = 0.3333333f; ks[1].outTangent = 0f; ks[1].outWeight = 0.3333333f; ks[1].weightedMode = WeightedMode.None; cuGravContinuous = new AnimationCurve(ks); } // ************************** // Time to cleanup ! // ************************** void CleanAssets() { // Cleaning instances of the fluids if (FluidInstance != null) Destroy(FluidInstance); } void OnEnable() { } void OnDisable() { if ( isSprayActive ) { StopCoroutine("SprayMultipleTimesRandomDuration"); StopCoroutine("SprayMultipleTimesCustom"); StopStream(); } } void OnDestroy() { CleanAssets(); // Unloading assetbundle AssetLoader.DoneWithAssetBundleFromFile(PLUGIN_PATH + FLUIDS_BUNDLE_PATH); } // *********************************************************** // EXTERNAL TOOLS - Thank you great coders for your content! // *********************************************************** // *********** MacGruber_Utils.cs START ********************* // Get directory path where the plugin is located. Based on Alazi's & VAMDeluxe's method. public static string GetPluginPath(MVRScript self) { string id = self.name.Substring(0, self.name.IndexOf('_')); string filename = self.manager.GetJSON()["plugins"][id].Value; return filename.Substring(0, filename.LastIndexOfAny(new char[] { '/', '\\' })); } // Get path prefix of the package that contains our plugin. public static string GetPackagePath(MVRScript self) { string id = self.name.Substring(0, self.name.IndexOf('_')); string filename = self.manager.GetJSON()["plugins"][id].Value; int idx = filename.IndexOf(":/"); if (idx >= 0) return filename.Substring(0, idx+2); else return string.Empty; } // Check if our plugin is running from inside a package public static bool IsInPackage(MVRScript self) { string id = self.name.Substring(0, self.name.IndexOf('_')); string filename = self.manager.GetJSON()["plugins"][id].Value; return filename.IndexOf(":/") >= 0; } } }