using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using SimpleJSON;
namespace Disa
{
// Manages trigger zones for orifices (e.g., Mouth, Vagina, Anus) on a selected Atom (character) in a Unity-based scene.
// Tracks penetration and proximity events, manages penis colliders for male atoms, and handles UI for configuration.
public class OrificeTriggerManager : MVRScript
{
public static OrificeTriggerManager singleton;
private Atom selectedAtom;
private List<object> uiElements;
private List<JSONStorableFloat> sliderStorables;
private List<JSONStorableBool> toggleStorables;
private List<JSONStorableString> textFieldStorables;
private List<UIDynamicButton> buttonElements;
private string selectedOrifice;
private Dictionary<string, Orifice> orifices;
private Dictionary<string, ProximityHandler> proximityHandlers;
private Dictionary<string, OrificeTriggerHandler> triggerHandlers;
private bool debugVisible = true;
private bool penisCollidersVisible = false;
private Dictionary<Atom, List<GameObject>> penisViz = new Dictionary<Atom, List<GameObject>>();
private JSONStorableString mouthStatus;
private JSONStorableString vaginaStatus;
private JSONStorableString anusStatus;
private JSONStorableString mouthProxStatus;
private JSONStorableString vaginaProxStatus;
private JSONStorableString anusProxStatus;
private List<Atom> maleAtoms;
public Dictionary<Atom, Penis> penises;
public Dictionary<string, Orifice> Orifices
{
get { return orifices; }
}
// Initializes the manager, sets up status tracking, finds male atoms, and creates the UI.
public override void Init()
{
singleton = this;
SuperController.LogMessage("OrificeTriggerManager: Initialization started");
uiElements = new List<object>();
sliderStorables = new List<JSONStorableFloat>();
toggleStorables = new List<JSONStorableBool>();
textFieldStorables = new List<JSONStorableString>();
buttonElements = new List<UIDynamicButton>();
orifices = new Dictionary<string, Orifice>();
proximityHandlers = new Dictionary<string, ProximityHandler>();
triggerHandlers = new Dictionary<string, OrificeTriggerHandler>();
selectedOrifice = "Mouth";
maleAtoms = new List<Atom>();
penises = new Dictionary<Atom, Penis>();
// Initialize status strings for tracking penetration and proximity
mouthStatus = new JSONStorableString("Mouth Status", "Penis in mouth: No");
vaginaStatus = new JSONStorableString("Vagina Status", "Penis in vagina: No");
anusStatus = new JSONStorableString("Anus Status", "Penis in anus: No");
mouthProxStatus = new JSONStorableString("Mouth Proximity", "Penis near mouth: No");
vaginaProxStatus = new JSONStorableString("Vagina Proximity", "Penis near vagina: No");
anusProxStatus = new JSONStorableString("Anus Proximity", "Penis near anus: No");
selectedAtom = containingAtom;
if (selectedAtom == null || selectedAtom.type != "Person")
{
SuperController.LogError("OrificeTriggerManager: This script must be added to a Person-type atom.");
return;
}
SuperController.LogMessage("OrificeTriggerManager: Atom automatically selected: " + selectedAtom.uid);
SelectAtom(selectedAtom.uid);
FindMaleAtoms();
CreateUI();
SuperController.LogMessage("OrificeTriggerManager: Initialization completed");
}
// Finds male atoms in the scene and initializes their penis colliders.
private void FindMaleAtoms()
{
maleAtoms.Clear();
penises.Clear();
penisViz.Clear();
List<Atom> allAtoms = SuperController.singleton.GetAtoms();
for (int i = 0; i < allAtoms.Count; i++)
{
Atom atom = allAtoms;
if (atom.type == "Person" && atom != selectedAtom)
{
if (HasPenis(atom))
{
maleAtoms.Add(atom);
penises[atom] = new Penis(atom);
SuperController.LogMessage("OrificeTriggerManager: Found male atom: " + atom.uid);
}
}
}
}
// Checks if an atom has penis-related components or colliders.
private bool HasPenis(Atom atom)
{
if (atom.GetStorableByID("penisTipControl") != null) return true;
if (atom.GetStorableByID("penisMidControl") != null) return true;
if (atom.GetStorableByID("penisBaseControl") != null) return true;
Collider[] colliders = atom.GetComponentsInChildren<Collider>();
foreach (var col in colliders)
{
string colNameLower = col.name.ToLower();
if ((colNameLower.Contains("autocollidergen") || colNameLower.Contains("autogen")) &&
(colNameLower.Contains("hard") || colNameLower.Contains("soft")))
{
Rigidbody[] rbs = col.GetComponentsInParent<Rigidbody>();
foreach (var rb in rbs)
{
if (rb.name.ToLower().Contains("penis"))
return true;
}
}
}
return false;
}
// Selects an atom and sets up its orifices and triggers.
public void SelectAtom(string atomUid)
{
selectedAtom = SuperController.singleton.GetAtomByUid(atomUid);
if (selectedAtom == null)
{
SuperController.LogError("OrificeTriggerManager: Atom " + atomUid + " not found.");
return;
}
SuperController.LogMessage("OrificeTriggerManager: Atom selected: " + atomUid);
orifices.Clear();
proximityHandlers.Clear();
triggerHandlers.Clear();
try
{
string[] orificeNames = new string[] { "Mouth", "Vagina", "Anus" };
for (int i = 0; i < orificeNames.Length; i++)
{
string orificeName = orificeNames;
Orifice orifice = selectedAtom.gameObject.AddComponent<Orifice>();
if (orifice != null)
{
orifice.Init(orificeName, selectedAtom);
if (orifice.rb != null)
{
orifices[orificeName] = orifice;
SetupTriggers(orifice, orificeName);
SuperController.LogMessage("OrificeTriggerManager: Created " + orificeName);
}
else
{
SuperController.LogError("OrificeTriggerManager: Failed to initialize " + orificeName + " due to missing Rigidbody");
UnityEngine.Object.Destroy(orifice);
}
}
else
{
SuperController.LogError("OrificeTriggerManager: Failed to create Orifice component for " + orificeName);
}
}
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Error creating orifices: " + e.Message);
}
UpdateAllStatuses();
CreateUI();
}
// Sets up trigger and proximity colliders for an orifice, including their position, scale, and visual debugging.
private void SetupTriggers(Orifice orifice, string name)
{
if (orifice == null || orifice.rb == null)
{
SuperController.LogError("OrificeTriggerManager: Orifice or Rigidbody null for " + name);
return;
}
try
{
// Create entry trigger
GameObject triggerGO = GameObject.CreatePrimitive(PrimitiveType.Capsule);
triggerGO.name = "BL_" + name + "_EnterTrigger";
CapsuleCollider enterTriggerCollider = triggerGO.GetComponent<CapsuleCollider>();
enterTriggerCollider.isTrigger = true;
triggerGO.transform.SetParent(orifice.rb.transform, false);
if (name == "Mouth")
{
triggerGO.transform.localRotation = Quaternion.Euler(90, 0, 0);
}
else if (name == "Anus")
{
triggerGO.transform.localRotation = Quaternion.Euler(25, 0, 0);
}
else
{
triggerGO.transform.localRotation = Quaternion.identity;
}
triggerGO.transform.localScale = new Vector3(orifice.triggerScale.val, orifice.triggerScale.val, orifice.triggerScale.val);
triggerGO.transform.localPosition = new Vector3(0f, orifice.triggerOffsetUp.val + orifice.defaultUp, orifice.triggerOffsetForward.val + orifice.defaultForward);
Renderer renderer = triggerGO.GetComponent<Renderer>();
if (renderer != null)
{
renderer.material = new Material(Shader.Find("Standard"));
renderer.material.color = new Color(0f, 0f, 1f, 0.5f);
renderer.enabled = debugVisible;
}
// Create proximity trigger
GameObject proximityGO = GameObject.CreatePrimitive(PrimitiveType.Capsule);
proximityGO.name = "BL_" + name + "_ProximityTrigger";
CapsuleCollider proximityTrigger = proximityGO.GetComponent<CapsuleCollider>();
proximityTrigger.isTrigger = true;
proximityGO.transform.SetParent(orifice.rb.transform, false);
if (name == "Mouth")
{
proximityGO.transform.localRotation = Quaternion.Euler(90, 0, 0);
}
else if (name == "Anus")
{
proximityGO.transform.localRotation = Quaternion.Euler(25, 0, 0);
}
else
{
proximityGO.transform.localRotation = Quaternion.identity;
}
proximityGO.transform.localScale = new Vector3(orifice.proximityScale.val, orifice.proximityScale.val, orifice.proximityScale.val);
proximityGO.transform.localPosition = new Vector3(0f, orifice.triggerOffsetUp.val + orifice.defaultUp, orifice.triggerOffsetForward.val + orifice.defaultForward);
Renderer proxRenderer = proximityGO.GetComponent<Renderer>();
if (proxRenderer != null)
{
proxRenderer.material = new Material(Shader.Find("Standard"));
proxRenderer.material.SetFloat("_Mode", 2);
proxRenderer.material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
proxRenderer.material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
proxRenderer.material.SetInt("_ZWrite", 0);
proxRenderer.material.DisableKeyword("_ALPHATEST_ON");
proxRenderer.material.EnableKeyword("_ALPHABLEND_ON");
proxRenderer.material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
proxRenderer.material.color = new Color(1f, 1f, 0f, 0.3f);
proxRenderer.enabled = debugVisible && orifice.showProximity.val;
}
OrificeTriggerHandler triggerHandler = triggerGO.AddComponent<OrificeTriggerHandler>();
if (triggerHandler != null)
{
triggerHandler.orifice = orifice;
triggerHandler.isActive = true;
triggerHandlers[name] = triggerHandler;
}
ProximityHandler proximityHandler = proximityGO.AddComponent<ProximityHandler>();
if (proximityHandler != null)
{
proximityHandler.orifice = orifice;
proximityHandler.on = true;
proximityHandlers[name] = proximityHandler;
}
orifice.enterTriggerCollider = enterTriggerCollider;
orifice.proximityTrigger = proximityTrigger;
orifice.showProximity.setCallbackFunction = (bool val) =>
{
if (proxRenderer != null) proxRenderer.enabled = debugVisible && val;
};
orifice.triggerOffsetUp.setCallbackFunction = (float val) =>
{
if (triggerGO != null) triggerGO.transform.localPosition = new Vector3(0f, val + orifice.defaultUp, orifice.triggerOffsetForward.val + orifice.defaultForward);
if (proximityGO != null) proximityGO.transform.localPosition = new Vector3(0f, val + orifice.defaultUp, orifice.triggerOffsetForward.val + orifice.defaultForward);
};
orifice.triggerOffsetForward.setCallbackFunction = (float val) =>
{
if (triggerGO != null) triggerGO.transform.localPosition = new Vector3(0f, orifice.triggerOffsetUp.val + orifice.defaultUp, val + orifice.defaultForward);
if (proximityGO != null) proximityGO.transform.localPosition = new Vector3(0f, orifice.triggerOffsetUp.val + orifice.defaultUp, val + orifice.defaultForward);
};
orifice.triggerScale.setCallbackFunction = (float val) =>
{
if (triggerGO != null) triggerGO.transform.localScale = new Vector3(val, val, val);
};
orifice.proximityScale.setCallbackFunction = (float val) =>
{
if (proximityGO != null) proximityGO.transform.localScale = new Vector3(val, val, val);
};
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Error in SetupTriggers for " + name + ": " + e.Message);
}
}
// Creates the UI for configuring orifices and toggling debug/penis collider visibility.
private void CreateUI()
{
ClearUI();
try
{
SetupButton("Mouth Settings", false, delegate { SelectOrifice("Mouth"); });
SetupButton("Vagina Settings", true, delegate { SelectOrifice("Vagina"); });
SetupButton("Anus Settings", false, delegate { SelectOrifice("Anus"); });
SetupButton("Debug", true, ToggleDebugVisibility);
SetupButton("Penis Colliders", true, TogglePenisColliders);
AddStatusUIElements();
SelectOrifice(selectedOrifice);
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Error creating UI: " + e.Message);
}
}
// Toggles visibility of trigger and proximity colliders for debugging.
private void ToggleDebugVisibility()
{
debugVisible = !debugVisible;
foreach (var pair in orifices)
{
Orifice orifice = pair.Value;
if (orifice.enterTriggerCollider != null)
{
Renderer renderer = orifice.enterTriggerCollider.gameObject.GetComponent<Renderer>();
if (renderer != null) renderer.enabled = debugVisible;
}
if (orifice.proximityTrigger != null)
{
Renderer proxRenderer = orifice.proximityTrigger.gameObject.GetComponent<Renderer>();
if (proxRenderer != null) proxRenderer.enabled = debugVisible && orifice.showProximity.val;
}
}
SuperController.LogMessage("OrificeTriggerManager: Trigger visibility " + (debugVisible ? "enabled" : "disabled"));
}
// Toggles visibility of penis colliders for debugging.
private void TogglePenisColliders()
{
penisCollidersVisible = !penisCollidersVisible;
if (penisCollidersVisible)
{
FindMaleAtoms();
for (int i = 0; i < maleAtoms.Count; i++)
{
CreatePenisColliderViz(maleAtoms);
}
}
else
{
HideAllPenisViz();
}
SuperController.LogMessage("OrificeTriggerManager: Penis collider visibility " + (penisCollidersVisible ? "enabled" : "disabled"));
}
// Creates visual representations for penis colliders on a male atom.
private void CreatePenisColliderViz(Atom male)
{
if (penisViz.ContainsKey(male))
{
List<GameObject> list = penisViz[male];
for (int i = 0; i < list.Count; i++)
{
GameObject v = list;
if (v != null)
{
v.SetActive(true);
Renderer r = v.GetComponent<Renderer>();
if (r != null) r.enabled = true;
}
}
return;
}
List<GameObject> vizList = new List<GameObject>();
Penis penis = penises.ContainsKey(male) ? penises[male] : null;
if (penis == null)
{
SuperController.LogMessage($"CreatePenisColliderViz: Penis object not found for {male.uid}");
return;
}
// Create transparent material once
Material transparentMat = new Material(Shader.Find("Standard"));
transparentMat.SetFloat("_Mode", 2);
transparentMat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
transparentMat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
transparentMat.SetInt("_ZWrite", 0);
transparentMat.DisableKeyword("_ALPHATEST_ON");
transparentMat.EnableKeyword("_ALPHABLEND_ON");
transparentMat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
transparentMat.color = new Color(1f, 0f, 0f, 0.3f); // Transparent red
// Find penis colliders
Collider[] allColliders = male.GetComponentsInChildren<Collider>();
foreach (var col in allColliders)
{
if (penis.IsPenisCollider(col))
{
// Skip standard penisBase, penisMid, penisTip colliders
string colNameLower = col.name.ToLower();
if (colNameLower.Contains("penisbase") || colNameLower.Contains("penismid") || colNameLower.Contains("penistip"))
{
SuperController.LogMessage($"CreatePenisColliderViz: Skipped standard collider {col.name} for {male.uid}");
continue;
}
GameObject viz = null;
// C# 6.0-compatible type checking
if (col is CapsuleCollider)
{
CapsuleCollider cc = (CapsuleCollider)col;
viz = GameObject.CreatePrimitive(PrimitiveType.Capsule);
viz.name = "PenisColliderViz_Capsule_" + col.name;
viz.transform.SetParent(col.transform, false);
viz.transform.localPosition = cc.center;
// Account for capsule direction
switch (cc.direction)
{
case 0: // X-axis
viz.transform.localRotation = Quaternion.Euler(0f, 0f, 90f);
break;
case 2: // Z-axis
viz.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
break;
default: // Y-axis
viz.transform.localRotation = Quaternion.identity;
break;
}
// Scale capsule
viz.transform.localScale = new Vector3(cc.radius * 2f, cc.height / 2f, cc.radius * 2f);
}
else if (col is SphereCollider)
{
SphereCollider sc = (SphereCollider)col;
viz = GameObject.CreatePrimitive(PrimitiveType.Sphere);
viz.name = "PenisColliderViz_Sphere_" + col.name;
viz.transform.SetParent(col.transform, false);
viz.transform.localPosition = sc.center;
viz.transform.localRotation = Quaternion.identity;
viz.transform.localScale = Vector3.one * (sc.radius * 2f);
}
else if (col is MeshCollider)
{
MeshCollider mc = (MeshCollider)col;
viz = new GameObject("PenisColliderViz_Mesh_" + col.name);
viz.transform.SetParent(col.transform, false);
viz.transform.localPosition = Vector3.zero;
viz.transform.localRotation = Quaternion.identity;
viz.transform.localScale = Vector3.one;
MeshFilter mf = viz.AddComponent<MeshFilter>();
mf.sharedMesh = mc.sharedMesh;
MeshRenderer mr = viz.AddComponent<MeshRenderer>();
mr.material = transparentMat;
mr.enabled = true;
}
if (viz != null)
{
// Remove collider from visualization to avoid interfering with physics
Collider vizCol = viz.GetComponent<Collider>();
if (vizCol != null) UnityEngine.Object.Destroy(vizCol);
// Apply transparent material for primitives
Renderer r = viz.GetComponent<Renderer>();
if (r != null)
{
r.material = transparentMat;
r.enabled = true;
}
vizList.Add(viz);
}
}
}
if (vizList.Count > 0)
{
penisViz[male] = vizList;
SuperController.LogMessage($"CreatePenisColliderViz: Created {vizList.Count} visualizations for {male.uid}");
}
else
{
SuperController.LogMessage($"CreatePenisColliderViz: No CUA penis colliders found for {male.uid}");
}
}
// Hides all penis collider visualizations.
private void HideAllPenisViz()
{
foreach (var pair in penisViz)
{
List<GameObject> list = pair.Value;
if (list != null)
{
for (int i = 0; i < list.Count; i++)
{
GameObject v = list;
if (v != null)
{
v.SetActive(false);
Renderer r = v.GetComponent<Renderer>();
if (r != null) r.enabled = false;
}
}
}
}
}
// Updates the UI to show settings for the selected orifice.
private void SelectOrifice(string orificeName)
{
selectedOrifice = orificeName;
ClearUI();
try
{
SetupButton("Mouth Settings", false, delegate { SelectOrifice("Mouth"); });
SetupButton("Vagina Settings", true, delegate { SelectOrifice("Vagina"); });
SetupButton("Anus Settings", false, delegate { SelectOrifice("Anus"); });
SetupButton("Debug", true, ToggleDebugVisibility);
SetupButton("Penis Colliders", true, TogglePenisColliders);
AddStatusUIElements();
if (orifices.ContainsKey(orificeName))
{
Orifice orifice = orifices[orificeName];
var triggerScaleSlider = CreateSlider(orifice.triggerScale);
uiElements.Add(triggerScaleSlider);
sliderStorables.Add(orifice.triggerScale);
var triggerOffsetUpSlider = CreateSlider(orifice.triggerOffsetUp);
uiElements.Add(triggerOffsetUpSlider);
sliderStorables.Add(orifice.triggerOffsetUp);
var triggerOffsetForwardSlider = CreateSlider(orifice.triggerOffsetForward);
uiElements.Add(triggerOffsetForwardSlider);
sliderStorables.Add(orifice.triggerOffsetForward);
var proximityScaleSlider = CreateSlider(orifice.proximityScale);
uiElements.Add(proximityScaleSlider);
sliderStorables.Add(orifice.proximityScale);
var showProximityToggle = CreateToggle(orifice.showProximity);
uiElements.Add(showProximityToggle);
toggleStorables.Add(orifice.showProximity);
var infoText = CreateTextField(orifice.info);
uiElements.Add(infoText);
textFieldStorables.Add(orifice.info);
}
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Error selecting orifice: " + e.Message);
}
}
// Clears all UI elements from the scene.
private void ClearUI()
{
for (int i = 0; i < uiElements.Count; i++)
{
var element = uiElements;
if (element is UIDynamic) RemoveSpacer(element as UIDynamic);
if (element is UIDynamicSlider) RemoveSlider(element as UIDynamicSlider);
if (element is UIDynamicToggle) RemoveToggle(element as UIDynamicToggle);
if (element is UIDynamicTextField) RemoveTextField(element as UIDynamicTextField);
if (element is UIDynamicButton) RemoveButton(element as UIDynamicButton);
}
uiElements.Clear();
sliderStorables.Clear();
toggleStorables.Clear();
textFieldStorables.Clear();
buttonElements.Clear();
}
// Creates a UI button with the specified label and callback.
private void SetupButton(string label, bool rightSide, UnityAction action)
{
UIDynamicButton button = CreateButton(label, rightSide);
button.button.onClick.AddListener(action);
uiElements.Add(button);
buttonElements.Add(button);
}
// Adds status UI elements for displaying penetration and proximity states.
private void AddStatusUIElements()
{
var mouthStatusText = CreateTextField(mouthStatus);
uiElements.Add(mouthStatusText);
textFieldStorables.Add(mouthStatus);
var vaginaStatusText = CreateTextField(vaginaStatus);
uiElements.Add(vaginaStatusText);
textFieldStorables.Add(vaginaStatus);
var anusStatusText = CreateTextField(anusStatus);
uiElements.Add(anusStatusText);
textFieldStorables.Add(anusStatus);
var mouthProxText = CreateTextField(mouthProxStatus);
uiElements.Add(mouthProxText);
textFieldStorables.Add(mouthProxStatus);
var vaginaProxText = CreateTextField(vaginaProxStatus);
uiElements.Add(vaginaProxText);
textFieldStorables.Add(vaginaProxStatus);
var anusProxText = CreateTextField(anusProxStatus);
uiElements.Add(anusProxText);
textFieldStorables.Add(anusProxStatus);
}
// Updates the penetration status display for a specific orifice.
public void UpdateStatus(string orificeName)
{
JSONStorableString status = null;
string message = "";
switch (orificeName)
{
case "Mouth":
status = mouthStatus;
message = "Penis in mouth: ";
break;
case "Vagina":
status = vaginaStatus;
message = "Penis in vagina: ";
break;
case "Anus":
status = anusStatus;
message = "Penis in anus: ";
break;
}
if (status != null && orifices.ContainsKey(orificeName))
{
status.val = message + (orifices[orificeName].isPenetrated ? "Yes" : "No");
}
}
// Updates the proximity status display for a specific orifice.
public void UpdateProxStatus(string orificeName)
{
JSONStorableString status = null;
string message = "";
switch (orificeName)
{
case "Mouth":
status = mouthProxStatus;
message = "Penis near mouth: ";
break;
case "Vagina":
status = vaginaProxStatus;
message = "Penis near vagina: ";
break;
case "Anus":
status = anusProxStatus;
message = "Penis near anus: ";
break;
}
if (status != null && proximityHandlers.ContainsKey(orificeName))
{
ProximityHandler handler = proximityHandlers[orificeName];
bool inProx = handler != null && handler.isProx;
status.val = message + (inProx ? "Yes" : "No");
}
}
// Updates all penetration and proximity statuses.
public void UpdateAllStatuses()
{
foreach (var pair in orifices)
{
UpdateStatus(pair.Key);
UpdateProxStatus(pair.Key);
}
}
// Updates statuses every frame.
private void Update()
{
UpdateAllStatuses();
}
// Cleans up resources when the script is destroyed, including triggers, orifices, and penis visualizations.
public void OnDestroy()
{
try
{
foreach (var pair in orifices)
{
Orifice orifice = pair.Value;
if (orifice != null)
{
if (orifice.enterTriggerCollider != null)
{
UnityEngine.Object.Destroy(orifice.enterTriggerCollider.gameObject);
}
if (orifice.proximityTrigger != null)
{
UnityEngine.Object.Destroy(orifice.proximityTrigger.gameObject);
}
UnityEngine.Object.Destroy(orifice);
}
}
orifices.Clear();
proximityHandlers.Clear();
triggerHandlers.Clear();
foreach (var pair in penisViz)
{
List<GameObject> list = pair.Value;
if (list != null)
{
for (int i = 0; i < list.Count; i++)
{
GameObject v = list;
if (v != null) UnityEngine.Object.Destroy(v);
}
}
}
penisViz.Clear();
SuperController.LogMessage("OrificeTriggerManager: Components destroyed");
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Error in OnDestroy: " + e.Message);
}
ClearUI();
}
// Serializes the script's state to JSON for saving.
public override JSONClass GetJSON(bool includePhysical = true, bool includeAppearance = true, bool forceStore = true)
{
JSONClass jc = base.GetJSON(includePhysical, includeAppearance, forceStore);
try
{
foreach (var pair in orifices)
{
if (pair.Value != null)
{
jc[pair.Key] = pair.Value.Store("", includePhysical);
}
}
SuperController.LogMessage("OrificeTriggerManager: JSON saved");
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Serialization error: " + e.Message);
}
return jc;
}
// Restores the script's state from JSON for loading.
public override void RestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, JSONArray presetAtoms = null, bool setMissingToDefault = true)
{
base.RestoreFromJSON(jc, restorePhysical, restoreAppearance, presetAtoms, setMissingToDefault);
try
{
foreach (var pair in orifices)
{
if (jc.HasKey(pair.Key) && pair.Value != null)
{
pair.Value.Load(jc[pair.Key].AsObject, "");
}
}
UpdateAllStatuses();
SuperController.LogMessage("OrificeTriggerManager: JSON restored");
}
catch (Exception e)
{
SuperController.LogError("OrificeTriggerManager: Deserialization error: " + e.Message);
}
}
}
// Represents a penis on a male atom, managing its colliders for detection.
public class Penis
{
public Atom atom;
private HashSet<Collider> penisColliders;
private HashSet<string> penisColliderNames;
public Penis(Atom atom)
{
this.atom = atom;
penisColliders = new HashSet<Collider>();
penisColliderNames = new HashSet<string>();
FindPenisControllers();
FindCuaPenisColliders();
SuperController.LogMessage($"Penis: Initialized for {atom.uid}. Found colliders: {penisColliders.Count}");
}
// Finds penis-related controllers and their colliders.
private void FindPenisControllers()
{
Rigidbody[] rigidbodies = atom.GetComponentsInChildren<Rigidbody>();
foreach (Rigidbody rb in rigidbodies)
{
string rbNameLower = rb.name.ToLower();
bool isPenisPart = false;
if (rbNameLower.Contains("penisbase") || rbNameLower.Contains("penis_base")) isPenisPart = true;
else if (rbNameLower.Contains("penismid") || rbNameLower.Contains("penis_mid")) isPenisPart = true;
else if (rbNameLower.Contains("penistip") || rbNameLower.Contains("penis_tip") || rbNameLower.Contains("penistipcontrol")) isPenisPart = true;
if (isPenisPart)
{
Collider[] colliders = rb.GetComponents<Collider>();
foreach (var col in colliders)
{
penisColliders.Add(col);
penisColliderNames.Add(col.name);
SuperController.LogMessage($"Penis: Found controller collider: {col.name} from {rb.name}");
}
Collider[] childColliders = rb.GetComponentsInChildren<Collider>();
foreach (var col in childColliders)
{
if (!penisColliders.Contains(col))
{
penisColliders.Add(col);
penisColliderNames.Add(col.name);
SuperController.LogMessage($"Penis: Found child collider: {col.name} from {rb.name}");
}
}
}
}
}
// Finds custom (CUA) penis colliders.
private void FindCuaPenisColliders()
{
Collider[] allColliders = atom.GetComponentsInChildren<Collider>();
foreach (var col in allColliders)
{
string colNameLower = col.name.ToLower();
if ((colNameLower.Contains("autocollidergen") || colNameLower.Contains("autogen")) &&
(colNameLower.Contains("hard") || colNameLower.Contains("soft")) &&
!penisColliderNames.Contains(col.name))
{
Transform parent = col.transform.parent;
while (parent != null)
{
if (parent.name.ToLower().Contains("penis"))
{
penisColliders.Add(col);
penisColliderNames.Add(col.name);
SuperController.LogMessage($"Penis: Found CUA collider: {col.name}");
break;
}
parent = parent.parent;
}
}
}
}
// Checks if a collider belongs to the penis.
public bool IsPenisCollider(Collider other)
{
if (other == null) return false;
bool isPenisPart = penisColliders.Contains(other);
if (!isPenisPart)
{
string otherNameLower = other.name.ToLower();
isPenisPart = penisColliderNames.Contains(other.name) ||
(otherNameLower.Contains("autocollidergen") && otherNameLower.Contains("hard"));
}
return isPenisPart;
}
}
// Represents an orifice (e.g., Mouth, Vagina, Anus) with trigger and proximity colliders.
public class Orifice : MonoBehaviour
{
public string name;
public Rigidbody rb;
public JSONStorableFloat triggerScale;
public JSONStorableFloat triggerOffsetUp;
public JSONStorableFloat triggerOffsetForward;
public JSONStorableFloat proximityScale;
public JSONStorableBool showProximity;
public JSONStorableString info;
public CapsuleCollider enterTriggerCollider;
public CapsuleCollider proximityTrigger;
public int penetrationCount;
public bool isPenetrated { get { return penetrationCount > 0; } }
public float defaultUp;
public float defaultForward;
// Initializes the orifice with specific settings based on its name.
public virtual void Init(string orificeName, Atom atom)
{
name = orificeName;
try
{
string rbName;
float defaultScale;
switch (name)
{
case "Mouth":
rbName = "ThroatTrigger";
defaultScale = 0.03f;
defaultUp = 0.01f;
defaultForward = 0.07f;
break;
case "Vagina":
rbName = "VaginaTrigger";
defaultScale = 0.01f;
defaultUp = -0.08f;
defaultForward = 0.01f;
break;
case "Anus":
rbName = "pelvis";
defaultScale = 0.01f;
defaultUp = -0.16f;
defaultForward = -0.04f;
break;
default:
rbName = "pelvis";
defaultScale = 0.03f;
defaultUp = 0f;
defaultForward = 0f;
break;
}
var storable = atom.GetStorableByID(rbName);
if (storable != null)
{
rb = storable.GetComponent<Rigidbody>();
}
if (rb == null)
{
rb = atom.gameObject.AddComponent<Rigidbody>();
rb.isKinematic = true;
rb.useGravity = false;
SuperController.LogMessage("Orifice: Rigidbody created for " + name);
}
triggerScale = new JSONStorableFloat("Trigger Scale", defaultScale, 0.01f, 0.5f);
triggerOffsetUp = new JSONStorableFloat("Trigger Offset Up", 0f, -0.2f, 0.2f);
triggerOffsetForward = new JSONStorableFloat("Trigger Offset Forward", 0f, -0.2f, 0.2f);
proximityScale = new JSONStorableFloat("Proximity Scale", defaultScale * 1.5f, 0.01f, 1f);
showProximity = new JSONStorableBool("Show Proximity Zone", true);
info = new JSONStorableString("Information", "Orifice: " + name);
penetrationCount = 0;
SuperController.LogMessage("Orifice: Initialized " + name);
}
catch (Exception e)
{
SuperController.LogError("Orifice: Initialization error for " + name + ": " + e.Message);
}
}
// Serializes the orifice's state to JSON.
public virtual JSONClass Store(string key, bool includePhysical)
{
JSONClass jc = new JSONClass();
try
{
jc["triggerScale"] = triggerScale.val.ToString();
jc["triggerOffsetUp"] = triggerOffsetUp.val.ToString();
jc["triggerOffsetForward"] = triggerOffsetForward.val.ToString();
jc["proximityScale"] = proximityScale.val.ToString();
jc["showProximity"] = showProximity.val.ToString();
jc["info"] = info.val;
jc["penetrationCount"] = penetrationCount.ToString();
}
catch (Exception e)
{
SuperController.LogError("Orifice: Serialization error for " + key + ": " + e.Message);
}
return jc;
}
// Restores the orifice's state from JSON.
public virtual void Load(JSONClass jc, string key)
{
try
{
if (jc.HasKey("triggerScale")) triggerScale.val = float.Parse(jc["triggerScale"]);
if (jc.HasKey("triggerOffsetUp")) triggerOffsetUp.val = float.Parse(jc["triggerOffsetUp"]);
if (jc.HasKey("triggerOffsetForward")) triggerOffsetForward.val = float.Parse(jc["triggerOffsetForward"]);
if (jc.HasKey("proximityScale")) proximityScale.val = float.Parse(jc["proximityScale"]);
if (jc.HasKey("showProximity")) showProximity.val = bool.Parse(jc["showProximity"]);
if (jc.HasKey("info")) info.val = jc["info"];
if (jc.HasKey("penetrationCount")) penetrationCount = int.Parse(jc["penetrationCount"]);
}
catch (Exception e)
{
SuperController.LogError("Orifice: Deserialization error for " + key + ": " + e.Message);
}
}
}
// Handles proximity trigger events, tracking when penis colliders enter or exit the proximity zone.
public class ProximityHandler : MonoBehaviour
{
public Orifice orifice;
public bool on = true;
public bool isProx = false;
private float lastLogTime = 0f;
private const float LogInterval = 2f;
private HashSet<Collider> penisCollidersInProx = new HashSet<Collider>();
void OnTriggerEnter(Collider other)
{
if (!on || orifice == null) return;
if (IsPenisCollider(other))
{
if (penisCollidersInProx.Add(other))
{
SuperController.LogMessage($"[Prox] Penis entered {gameObject.name} for {orifice.name}");
isProx = true;
OrificeTriggerManager.singleton.UpdateProxStatus(orifice.name);
}
}
}
void OnTriggerExit(Collider other)
{
if (!on || orifice == null) return;
if (IsPenisCollider(other))
{
if (penisCollidersInProx.Remove(other))
{
SuperController.LogMessage($"[Prox] Penis exited {gameObject.name} for {orifice.name}");
isProx = penisCollidersInProx.Count > 0;
OrificeTriggerManager.singleton.UpdateProxStatus(orifice.name);
}
}
}
private bool IsPenisCollider(Collider col)
{
if (col == null) return false;
Atom atom = col.gameObject.GetComponentInParent<Atom>();
if (atom == null || !OrificeTriggerManager.singleton.penises.ContainsKey(atom)) return false;
return OrificeTriggerManager.singleton.penises[atom].IsPenisCollider(col);
}
}
// Handles trigger events for an orifice, tracking penetration count.
public class OrificeTriggerHandler : MonoBehaviour
{
public Orifice orifice;
public bool isActive = true;
private float lastLogTime = 0f;
private const float LogInterval = 2f;
private HashSet<Collider> penisCollidersInTrigger = new HashSet<Collider>();
void OnTriggerEnter(Collider other)
{
if (!isActive || orifice == null) return;
if (IsPenisCollider(other))
{
if (penisCollidersInTrigger.Add(other))
{
orifice.penetrationCount++;
SuperController.LogMessage($"[Trig] PENETRATION in {orifice.name}! Count: {orifice.penetrationCount} (collider: {other.name})");
OrificeTriggerManager.singleton.UpdateStatus(orifice.name);
}
}
}
void OnTriggerExit(Collider other)
{
if (!isActive || orifice == null) return;
if (IsPenisCollider(other))
{
if (penisCollidersInTrigger.Remove(other))
{
orifice.penetrationCount--;
if (orifice.penetrationCount < 0) orifice.penetrationCount = 0;
SuperController.LogMessage($"[Trig] Exit from {orifice.name}. Count: {orifice.penetrationCount} (collider: {other.name})");
OrificeTriggerManager.singleton.UpdateStatus(orifice.name);
}
}
}
private bool IsPenisCollider(Collider col)
{
if (col == null) return false;
Atom atom = col.gameObject.GetComponentInParent<Atom>();
if (atom == null || !OrificeTriggerManager.singleton.penises.ContainsKey(atom)) return false;
return OrificeTriggerManager.singleton.penises[atom].IsPenisCollider(col);
}
}
}