• Hi Guest!

    We are extremely excited to announce the release of our first Beta1.1 and the first release of our Public AddonKit!
    To participate in the Beta, a subscription to the Entertainer or Creator Tier is required. For access to the Public AddonKit you must be a Creator tier member. Once subscribed, download instructions can be found here.

    Click here for information and guides regarding the VaM2 beta. Join our Discord server for more announcements and community discussion about VaM2.
  • Hi Guest!

    VaM2 Resource Categories have now been added to the Hub! For information on posting VaM2 resources and details about VaM2 related changes to our Community Forums, please see our official announcement here.
BodyLanguage

Plugins + Scripts BodyLanguage

Download [<1 MB]
So I really don’t know where the problem comes from, because I just loaded another environment with everything identical (plugins, clothes, post-processing, etc.) and it works without any issue. I already ran into this problem once before, and when it happens, even if you reboot VAM it’s like the scene gets corrupted as long as BodyLanguage is active.


Anyway, thanks a lot for taking the time to answer and for this amazing plugin. By the way, what new stuff is planned for V72?
Are you enabling and disabling BL in your scene? I thought you weren't supposed to do that. You know what the old doctor says, "If it hurts when you do dat, den don't do dat."

I've noticed that the character transparency plugin does weird things if you look through the transparent area. It might also have something to do with a CUA Vamifier plugin. Check for that.
 
Hi Chesse. I have an idea that I think would be interesting to implement. I would like to share it with you. Perhaps you will be interested. It's just that you have made a pretty powerful product and it seems to me that all the other plugins and their authors are far inferior to you in professionalism. The idea is as follows. I would like to see, and many would support me in this, that the interactions of Atoms with each other were more lively. It would be cool if a female character could move or reach out with her hands and specifically touch herself or a neighboring character. The speed of hand movement in space to the point of touch can be adjusted with a randomizer and a slider. The distance that can be moved with hands would be limited by an invisible sphere or a trigger like Empty and everything that goes beyond it does not fall under the targets. This would not allow the Atom to reach out endlessly and spoil or knock down the pose. Further, all Atoms in VAM have points with which we control the state of Atoms in space. These points could be just targets that could be chosen randomly for hand interaction, for example, and also make checkboxes for selecting specific touch points with another Atom.
You already have a setting for BJ when the hand itself aligns with the penis. This could be used when touching the body. Plus, I use the Dynamic Limbs plugin - a simple plugin for grabbing anything with your hand.
Such an implementation would make the scenes in VAM as lively as possible.
Thanks for the idea. Maybe at some later point, but right now I have enough stuff to finish. You know about the Movement>Slap&Caress feature, right? This is essentially what you want, but you have to tell it what to grab per pose.
 
Hi Cheese. I couldn't wait for you to start working on the plugin I asked for and decided to make it myself. I'm still in the process. I'm working on a plugin that allows you to move your hands around the body, there is a second plugin paired with this one, it does slaps on the face, butt, etc., but I've been working on it less so far. When I finish the work, I'll give it to you so you can implement it in your BL plugin. If you're interested in learning more, write me a personal message, I'll send you the files with the code.

I also have a problem with positioning the hand near the penis. I would like to ask you to give me this file and tell me which piece of code is responsible for this. I'll be glad if you help. I just dug through your files, but haven't found it yet and am more busy with my plugin.
And it would be nice if you shared the friction mode. I would be grateful to you.

 
Sorry to bother with this again, but didn’t receive any answer to my last question regarding the built-in expressions, where the eyebrows and lips sometimes twitch/spasm. I tried BL on 1.22.0.3 and 1.22.0.12, both clean installs, w/o other scene, session or person plugins, also tried different physics settings. Maybe its the current state of things (then Im sure someone else must have noticed it as well, the human brain is quite sensitive when it comes to detect mimics changes :)), if someone could confirm, then I stop searching for a solution. And maybe it would also be useful to make a list of expressions controlled by BL, which we could toggle inside the plugin?... Thanks very much
EDIT: Also noticed distortions of New teeth by Jackaroo, seems like some AshAuryn's expressions cause that. The ereskigal_front_teeth_fix.dsf provided by Jackaroo doesn't exactly fix this (partially fixes the incision, but deforms the interproximates and shrinks the teeth). Any ideas how to deal with that (besides not using AA expressions obviously, but that is not an option in BL)?
 
Last edited:
Sorry to bother with this again, but didn’t receive any answer to my last question regarding the built-in expressions, where the eyebrows and lips sometimes twitch/spasm. I tried BL on 1.22.0.3 and 1.22.0.12, both clean installs, w/o other scene, session or person plugins, also tried different physics settings. Maybe its the current state of things (then Im sure someone else must have noticed it as well, the human brain is quite sensitive when it comes to detect mimics changes :)), if someone could confirm, then I stop searching for a solution. And maybe it would also be useful to make a list of expressions controlled by BL, which we could toggle inside the plugin?... Thanks very much
EDIT: Also noticed distortions of New teeth by Jackaroo, seems like some AshAuryn's expressions cause that. The ereskigal_front_teeth_fix.dsf provided by Jackaroo doesn't exactly fix this (partially fixes the incision, but deforms the interproximates and shrinks the teeth). Any ideas how to deal with that (besides not using AA expressions obviously, but that is not an option in BL)?
Twitching is definitely not normal/the state of things. See my update videos for how it should look. Twitching sounds to me like there is a conflict with another plugin trying to do similar things, but since you tried without any other plugins idk. My last bet is the native auto expressions from the Auto Behaviors tab. It has to be disabled.

There are hundreds of morphs used by BL. If you don't want specific ones, you can delete them from the resource package.
 
Подергивание — это определённо ненормально. Посмотрите мои видеообзоры обновлений, чтобы узнать, как это должно выглядеть. Подергивание, похоже, конфликтует с другим плагином, пытающимся делать то же самое, но раз уж вы пробовали без других плагинов, то я не знаю. Последний вариант — нативные автоматические выражения из вкладки «Автоматическое поведение». Его нужно отключить.

В BL используются сотни морфов. Если вам не нужны какие-то определённые, вы можете удалить их из пакета ресурсов.
Thanks for answering, I'll try. It was only the first evening when I made this plugin. But I feel a strong lack of experience. I've come a long way, but I had to step back and while I'm redoing everything. I moved away from dots and moved to splines and body normals. But I made a bunch of mistakes and dependencies in new plugins. It began to destroy my code. It's painful to go back and sort it all out. All this turned out to be not so simple, but I like it, I will move on. And C# is not my native language.
And I slowly figured out the structure of your files. I found what I need and continue to explore.
There is a big one ahead. But I like to see my result and overcome difficulties. I will definitely try to bring everything to perfection.
And if you don't mind, I will sometimes share my results with you.
I just couldn't think that I myself can do in VAM what I like and need.
 
Twitching is definitely not normal/the state of things. See my update videos for how it should look. Twitching sounds to me like there is a conflict with another plugin trying to do similar things, but since you tried without any other plugins idk. My last bet is the native auto expressions from the Auto Behaviors tab. It has to be disabled.

There are hundreds of morphs used by BL. If you don't want specific ones, you can delete them from the resource package.
Thanks a lot for answering, Cheesy. I never really used Auto Expressions, tried turning everything else off in Auto Behavior also. It's weird it happens on clean builds too, have no idea what else to toggle :) I uploaded a video to illustrate the issue (model not default here, but with the default one, same story...) BTW tried removing AshAuryn's expressions from general.json and the teeth still get deformed, so some other expressions are breaking them too, but this would rather be a question for Jackaroo or other morph creators.
 
Thanks a lot for answering, Cheesy. I never really used Auto Expressions, tried turning everything else off in Auto Behavior also. It's weird it happens on clean builds too, have no idea what else to toggle :) I uploaded a video to illustrate the issue (model not default here, but with the default one, same story...) BTW tried removing AshAuryn's expressions from general.json and the teeth still get deformed, so some other expressions are breaking them too, but this would rather be a question for Jackaroo or other morph creators.
Hmm, no clue what's wrong here. Is this with default settings in ReadMyLips? I've never experienced such behavior with my plugin.
As I said, twitching usually is a sign that multiple things try to adjust the exact same morphs, but since BL uses its own set of expression morphs (duplicates of the originals in the resource pack with unique names) one had to manually target those "BL_..."-morphs through other means to produce a conflict.
What happens if you disable the expressions in RML? Is there still some expression movement?
I have everything enabled except the Auto Expressions and Auto Breast:
1756757517719.png


No, I meant that you should delete the morph files themselves in the resource pack found under Custom\Atom\Person\Morphs\female\CheesyFX\BodyLanguage\Expressions\.... Removing entries from the json doesn't prevent said morphs from being used. They merely use the default settings then.
 
Last edited:
Thanks for answering, I'll try. It was only the first evening when I made this plugin. But I feel a strong lack of experience. I've come a long way, but I had to step back and while I'm redoing everything. I moved away from dots and moved to splines and body normals. But I made a bunch of mistakes and dependencies in new plugins. It began to destroy my code. It's painful to go back and sort it all out. All this turned out to be not so simple, but I like it, I will move on. And C# is not my native language.
And I slowly figured out the structure of your files. I found what I need and continue to explore.
There is a big one ahead. But I like to see my result and overcome difficulties. I will definitely try to bring everything to perfection.
And if you don't mind, I will sometimes share my results with you.
I just couldn't think that I myself can do in VAM what I like and need.
Yeah, I was just going to advise you to target the Rigidbodies instead of the FreeControllerV3 - they can be anywhere, far off or inside the body. But even then it's not a trivial task to pick the right spot on the Rigidbody capsule that is closest to the female hand. I'd probably use Collider.ClosestPoint(hand.position) to get a meaningful target.

Everything regarding the handjob (aligning, grabbing, stroking, etc.) can be found inside FillMeUp/Hand.cs.
 
Daamn, that’s a straight-up banger! The transitions between each animation look smooth as hell… feels like this shit’s about to flip my whole animation game. You got a release date already, or you keepin’ it on the low? 😏
Thanks :) Sadly, I can't tell when this is ready. I'm working on v72 for over a year now...
Most features are somewhat complete but there are still some things I'm stuck with. And the huge developing time causes me to constantly forget about the issues left, haha. I'm not that organized ;)

Feel free to join my discord to see some more update posts or get in touch. https://discord.gg/2JJNgzNw
 
Can you tell me. I have problems defining vaginaControl for a female character via HasValidGenitalTarget. I can't find it in the scene. Tell me how to solve this issue or what to replace it with, it will make my life much easier. Can other plugins block its work and where can I find it in the settings? Is there an alternative?
 
Can you tell me. I have problems defining vaginaControl for a female character via HasValidGenitalTarget. I can't find it in the scene. Tell me how to solve this issue or what to replace it with, it will make my life much easier. Can other plugins block its work and where can I find it in the settings? Is there an alternative?
HasValidGenitalTarget? I don't understand, sorry. The vagina does not have a FreeControllerV3, if you meant that. What do you mean by "defining"? Do you want to add your own custom controllers?
 
HasValidGenitalTarget? I don't understand, sorry. The vagina does not have a FreeControllerV3, if you meant that. What do you mean by "defining"? Do you want to add your own custom controllers?
1756777247202.png

Sorry, I already found it in morphs. My controllers couldn't find a female character without vaginacontrol/ djn toy id in formfs Custom/Atom/Person/Morphs/female_genitalia/AUTO/[MU] HZY genitalia 1.441.vmi
 
View attachment 521361
Sorry, I already found it in morphs. My controllers couldn't find a female character without vaginacontrol/ djn toy id in formfs Custom/Atom/Person/Morphs/female_genitalia/AUTO/[MU] HZY genitalia 1.441.vmi
I don't have any clue what you're talking about, seriously. HasValidGenitalTarget? vaginaControl? [MU] HZY genitalia 1.441?
Anyways, I'm glad you found what you were looking for.
 
Hmm, no clue what's wrong here. Is this with default settings in ReadMyLips? I've never experienced such behavior with my plugin.
As I said, twitching usually is a sign that multiple things try to adjust the exact same morphs, but since BL uses its own set of expression morphs (duplicates of the originals in the resource pack with unique names) one had to manually target those "BL_..."-morphs through other means to produce a conflict.
What happens if you disable the expressions in RML? Is there still some expression movement?
I have everything enabled except the Auto Expressions and Auto Breast:
View attachment 521311

No, I meant that you should delete the morph files themselves in the resource pack found under Custom\Atom\Person\Morphs\female\CheesyFX\BodyLanguage\Expressions\.... Removing entries from the json doesn't prevent said morphs from being used. They merely use the default settings then.
When I disable expressions in RML, they slowly go to 0 and then there are no other expressions, when I check under active morphs (besides eye movements controlled by BL's Gaze and mouth open controlled by BL's VAMMoan), tested on clean build and with default BL settings. Tried running from CheesyPluginSuite10, on different PCs (might some VAM registry setting be the culprit?), lower res (FullHD), different physics and joint control settings, also tried capping the FPS 60,120,144 (some games have weird PhysX bugs when played with unlimited FPS), but no luck, still twitchy. Just to be sure, there are no other dependencies to BL besides CheesyFX.BodyLanguage_Resources.1.var or VAM build modifications needed? Adding latest FocusOnMe and VAMMoan didn't help either. Thanks again
 
Last edited:
When I disable expressions in RML, they slowly go to 0 and then there are no other expressions, when I check under active morphs (besides eye movements controlled by BL's Gaze and mouth open controlled by BL's VAMMoan), tested on clean build and with default BL settings. Tried running from CheesyPluginSuite10, on different PCs (might some VAM registry setting be the culprit?), lower res (FullHD), different physics and joint control settings, also tried capping the FPS 60,120,144 (some games have weird PhysX bugs when played with unlimited FPS), but no luck, still twitchy. Just to be sure, there are no other dependencies to BL besides CheesyFX.BodyLanguage_Resources.1.var or VAM build modifications needed? Adding latest FocusOnMe and VAMMoan didn't help either. Thanks again
Nothing besides FPS should have an effect on how the morphs are being played. FPS determines how ofter they are updated, but your fps seem high enough. And even with very low fps in some scenes I've never experienced your issue.

What you can do to verfy the problem: Observe the "BL_"...-morphs closely in the moph tab. They should change in a smooth fashion, with no visible jumps on the sliders.

Another thing you could try: Extract everything. BodyLanguage itself as well as the resource pack. Then install the local cslist on the female. BL will now use the local morphs as well. This way you can make sure that BL is the only thing messing with its morphs (because the local ones were not present before). If everything is like you were telling me though, this shouldn't change much.
 
Привет, Cheese! Оцени мой первый плагин. Буду рад, если он будет полезен тебе в твоих скриптах. https://hub.virtamate.com/resources...og-or-diviningforeskin-or-bodylanguage.60498/

с использованием Системы;
с использованием 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}");
}
}
}
[/СПОЙЛЕР]
 

Attachments

  • online-screen-recorder-2025-09-03--20-26-54.mp4
    45.8 MB
Last edited:
Много всего, но главная особенность — автоматизированная смена поз.
View attachment 520102
By the way, I want to try to do the same thing on the weekend, if I have time. I think it will take me 2-3 days, because I'm a newbie and don't have pretty windows like you do. But I think I'll also acquire my own automation, just like you. I want to try to do a lot, it's a pity that I don't have 10 hands. hehe
 
Back
Top Bottom