mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2026-01-01 14:17:31 +00:00
[PlapPlapForAll] initial commit
This commit is contained in:
parent
e54e50e76d
commit
7c3a8f4f39
9 changed files with 765 additions and 0 deletions
146
.Experimental/PlapPlapForAll/Components/DPS.cs
Normal file
146
.Experimental/PlapPlapForAll/Components/DPS.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
using ABI_RC.Core;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.PlapPlapForAll;
|
||||||
|
|
||||||
|
public enum DPSLightType
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
Hole,
|
||||||
|
Ring,
|
||||||
|
Normal,
|
||||||
|
Tip
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DPSOrifice
|
||||||
|
{
|
||||||
|
public DPSLightType type;
|
||||||
|
public Light dpsLight;
|
||||||
|
public Light normalLight;
|
||||||
|
public Transform basis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DPSPenetrator
|
||||||
|
{
|
||||||
|
public Transform penetratorTransform;
|
||||||
|
public float length; // _Length // _TPS_PenetratorLength
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DPS
|
||||||
|
{
|
||||||
|
private static readonly int Length = Shader.PropertyToID("_Length");
|
||||||
|
private static readonly int TpsPenetratorLength = Shader.PropertyToID("_TPS_PenetratorLength");
|
||||||
|
|
||||||
|
public static DPSLightType GetOrificeType(Light light)
|
||||||
|
{
|
||||||
|
int encoded = Mathf.RoundToInt(Mathf.Repeat((light.range * 500f) + 500f, 50f) + 200f);
|
||||||
|
return encoded switch
|
||||||
|
{
|
||||||
|
205 => DPSLightType.Hole, // 0.41
|
||||||
|
210 => DPSLightType.Ring, // 0.42
|
||||||
|
225 => DPSLightType.Normal, // 0.45
|
||||||
|
245 => DPSLightType.Tip, // 0.49
|
||||||
|
_ => DPSLightType.Invalid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ScanForDPS(
|
||||||
|
GameObject rootObject,
|
||||||
|
out List<DPSOrifice> dpsOrifices,
|
||||||
|
out bool foundPenetrator)
|
||||||
|
{
|
||||||
|
dpsOrifices = null;
|
||||||
|
foundPenetrator = false;
|
||||||
|
|
||||||
|
// Scan for DPS
|
||||||
|
Light[] allLights = rootObject.GetComponentsInChildren<Light>(true);
|
||||||
|
int lightCount = allLights.Length;
|
||||||
|
if (lightCount == 0) return false;
|
||||||
|
|
||||||
|
// DPS setups are usually like this:
|
||||||
|
// - Empty Container
|
||||||
|
// - Light (Type light) (range set to 0.x1 for hole or 0.x2 for ring)
|
||||||
|
// - Light (Normal light) (range set to 0.x5)
|
||||||
|
|
||||||
|
for (int i = 0; i < lightCount; i++)
|
||||||
|
{
|
||||||
|
Light light = allLights[i];
|
||||||
|
|
||||||
|
DPSLightType orificeType = GetOrificeType(light);
|
||||||
|
|
||||||
|
if (orificeType == DPSLightType.Tip)
|
||||||
|
{
|
||||||
|
foundPenetrator = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orificeType is DPSLightType.Hole or DPSLightType.Ring)
|
||||||
|
{
|
||||||
|
Transform lightTransform = light.transform;
|
||||||
|
Transform parent = lightTransform.parent;
|
||||||
|
|
||||||
|
// Found a DPS light
|
||||||
|
DPSOrifice dpsOrifice = new()
|
||||||
|
{
|
||||||
|
type = orificeType,
|
||||||
|
dpsLight = light,
|
||||||
|
normalLight = null,
|
||||||
|
basis = parent // Assume parent is basis
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to find normal light sibling
|
||||||
|
foreach (Transform sibling in parent)
|
||||||
|
{
|
||||||
|
if (sibling == lightTransform) continue;
|
||||||
|
if (sibling.TryGetComponent(out Light siblingLight)
|
||||||
|
&& GetOrificeType(siblingLight) == DPSLightType.Normal)
|
||||||
|
{
|
||||||
|
dpsOrifice.normalLight = siblingLight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dpsOrifices ??= [];
|
||||||
|
dpsOrifices.Add(dpsOrifice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dpsOrifices is { Count: > 0 } || foundPenetrator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AttemptTPSHack(GameObject rootObject)
|
||||||
|
{
|
||||||
|
Renderer[] allRenderers = rootObject.GetComponentsInChildren<Renderer>(true);
|
||||||
|
int count = allRenderers.Length;
|
||||||
|
if (count == 0) return;
|
||||||
|
|
||||||
|
SkinnedDickFixRoot fixRoot = rootObject.AddComponent<SkinnedDickFixRoot>();
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Renderer render = allRenderers[i];
|
||||||
|
if (render.gameObject.CompareTag(CVRTags.InternalObject))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Material mat = render.sharedMaterial;
|
||||||
|
if (!mat) continue;
|
||||||
|
|
||||||
|
float length = 0f;
|
||||||
|
if (mat.HasProperty(TpsPenetratorLength)) length += mat.GetFloat(TpsPenetratorLength);
|
||||||
|
if (mat.HasProperty(Length)) length += mat.GetFloat(Length);
|
||||||
|
if (length <= 0f) continue;
|
||||||
|
|
||||||
|
Transform dpsRoot;
|
||||||
|
SkinnedMeshRenderer smr = render as SkinnedMeshRenderer;
|
||||||
|
if (smr && smr.rootBone)
|
||||||
|
dpsRoot = smr.rootBone;
|
||||||
|
else
|
||||||
|
dpsRoot = render.transform;
|
||||||
|
|
||||||
|
fixRoot.Register(render, dpsRoot, length);
|
||||||
|
|
||||||
|
PlapPlapForAllMod.Logger.Msg(
|
||||||
|
$"Added shared DPS penetrator light for mesh '{render.name}' in object '{rootObject.name}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
.Experimental/PlapPlapForAll/Components/DickFix.cs
Normal file
126
.Experimental/PlapPlapForAll/Components/DickFix.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.PlapPlapForAll;
|
||||||
|
|
||||||
|
public sealed class SkinnedDickFixRoot : MonoBehaviour
|
||||||
|
{
|
||||||
|
private struct Entry
|
||||||
|
{
|
||||||
|
public Transform root;
|
||||||
|
public GameObject lightObject;
|
||||||
|
public Renderer[] renderers;
|
||||||
|
public int rendererCount;
|
||||||
|
public bool lastState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entry[] _entries;
|
||||||
|
private int _entryCount;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_entries = new Entry[4];
|
||||||
|
_entryCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(Renderer renderer, Transform dpsRoot, float length)
|
||||||
|
{
|
||||||
|
int idx = FindEntry(dpsRoot);
|
||||||
|
if (idx < 0)
|
||||||
|
{
|
||||||
|
idx = _entryCount;
|
||||||
|
if (idx == _entries.Length)
|
||||||
|
{
|
||||||
|
Entry[] old = _entries;
|
||||||
|
_entries = new Entry[old.Length << 1];
|
||||||
|
Array.Copy(old, _entries, old.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject lightObj = new("[PlapPlapForAllMod] Auto DPS Tip Light");
|
||||||
|
lightObj.transform.SetParent(dpsRoot, false);
|
||||||
|
lightObj.transform.localPosition = new Vector3(0f, 0f, length * 0.5f); // Noachi said should be at base
|
||||||
|
lightObj.SetActive(false); // Initially off
|
||||||
|
|
||||||
|
Light l = lightObj.AddComponent<Light>();
|
||||||
|
l.type = LightType.Point;
|
||||||
|
l.range = 0.49f;
|
||||||
|
l.intensity = 0.354f;
|
||||||
|
l.shadows = LightShadows.None;
|
||||||
|
l.renderMode = LightRenderMode.ForceVertex;
|
||||||
|
l.color = new Color(0.003921569f, 0.003921569f, 0.003921569f);
|
||||||
|
|
||||||
|
Entry e;
|
||||||
|
e.root = dpsRoot;
|
||||||
|
e.lightObject = lightObj;
|
||||||
|
e.renderers = new Renderer[2];
|
||||||
|
e.rendererCount = 0;
|
||||||
|
e.lastState = false;
|
||||||
|
|
||||||
|
_entries[idx] = e;
|
||||||
|
_entryCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref Entry entry = ref _entries[idx];
|
||||||
|
Renderer[] list = entry.renderers;
|
||||||
|
|
||||||
|
for (int i = 0; i < entry.rendererCount; i++)
|
||||||
|
if (list[i] == renderer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (entry.rendererCount == list.Length)
|
||||||
|
{
|
||||||
|
Renderer[] old = list;
|
||||||
|
list = new Renderer[old.Length << 1];
|
||||||
|
Array.Copy(old, list, old.Length);
|
||||||
|
entry.renderers = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
list[entry.rendererCount++] = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindEntry(Transform root)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _entryCount; i++)
|
||||||
|
if (_entries[i].root == root)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _entryCount; i++)
|
||||||
|
{
|
||||||
|
ref Entry entry = ref _entries[i];
|
||||||
|
|
||||||
|
bool active = false;
|
||||||
|
Renderer[] list = entry.renderers;
|
||||||
|
int count = entry.rendererCount;
|
||||||
|
|
||||||
|
for (int r = 0; r < count; r++)
|
||||||
|
{
|
||||||
|
Renderer ren = list[r];
|
||||||
|
if (!ren) continue;
|
||||||
|
if (ren.enabled && ren.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
active = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active != entry.lastState)
|
||||||
|
{
|
||||||
|
entry.lastState = active;
|
||||||
|
entry.lightObject.SetActive(active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _entryCount; i++)
|
||||||
|
{
|
||||||
|
ref Entry entry = ref _entries[i];
|
||||||
|
entry.lightObject.SetActive(false);
|
||||||
|
entry.lastState = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
271
.Experimental/PlapPlapForAll/Components/PlapPlapTap.cs
Normal file
271
.Experimental/PlapPlapForAll/Components/PlapPlapTap.cs
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Animations;
|
||||||
|
|
||||||
|
namespace NAK.PlapPlapForAll;
|
||||||
|
|
||||||
|
public enum PlapPlapAudioMode
|
||||||
|
{
|
||||||
|
Ass,
|
||||||
|
Mouth,
|
||||||
|
Generic,
|
||||||
|
Vagina
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlapPlapTap : MonoBehaviour
|
||||||
|
{
|
||||||
|
private static readonly Rule[] Rules =
|
||||||
|
{
|
||||||
|
new(PlapPlapAudioMode.Mouth, HumanBodyBones.Head, 3,
|
||||||
|
"mouth", "oral", "blow", "bj"),
|
||||||
|
new(PlapPlapAudioMode.Vagina, HumanBodyBones.Hips, 3,
|
||||||
|
"pussy", "vagina", "cunt", "v"),
|
||||||
|
new(PlapPlapAudioMode.Ass, HumanBodyBones.Hips, 2, "ass",
|
||||||
|
"anus", "butt", "anal", "b"),
|
||||||
|
new(PlapPlapAudioMode.Generic, null, 1,
|
||||||
|
"thigh", "armpit", "foot", "knee", "paizuri", "buttjob", "breast", "boob", "ear")
|
||||||
|
};
|
||||||
|
|
||||||
|
private DPSOrifice _dpsOrifice;
|
||||||
|
private Animator _animator;
|
||||||
|
private ParentConstraint _constraint;
|
||||||
|
private bool _dynamic;
|
||||||
|
private bool _lastLightState;
|
||||||
|
private int _activeSourceIndex = -1;
|
||||||
|
private PlapPlapAudioMode _mode;
|
||||||
|
private GameObject _plapPlapObject;
|
||||||
|
private RenderTexture _memoryTexture;
|
||||||
|
private bool _hasInitialized;
|
||||||
|
|
||||||
|
public static bool IsBuiltInPlapPlapSetup(DPSOrifice dpsOrifice)
|
||||||
|
{
|
||||||
|
Transform basis = dpsOrifice.basis;
|
||||||
|
|
||||||
|
// Check if basis name is plap plap
|
||||||
|
if (basis.name == "plap plap") return true;
|
||||||
|
|
||||||
|
// Check if there is a texture property parser under the basis
|
||||||
|
SkinnedMeshRenderer smr = basis.GetComponentInChildren<SkinnedMeshRenderer>(true);
|
||||||
|
if (smr && smr.sharedMaterial && smr.sharedMaterial.name == "Unlit_detect dps")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlapPlapTap CreateFromOrifice(DPSOrifice dpsOrifice, Animator animator, GameObject plapPlapPrefab)
|
||||||
|
{
|
||||||
|
Light light = dpsOrifice.dpsLight;
|
||||||
|
PlapPlapTap tap = light.gameObject.AddComponent<PlapPlapTap>();
|
||||||
|
tap._dpsOrifice = dpsOrifice;
|
||||||
|
tap._animator = animator;
|
||||||
|
|
||||||
|
GameObject plapPlap = Instantiate(plapPlapPrefab, light.transform, false);
|
||||||
|
tap._plapPlapObject = plapPlap;
|
||||||
|
|
||||||
|
// Duplicate memory texture
|
||||||
|
CVRTexturePropertyParser parser = plapPlap.GetComponentInChildren<CVRTexturePropertyParser>(true);
|
||||||
|
Camera camera = plapPlap.GetComponentInChildren<Camera>(true);
|
||||||
|
|
||||||
|
RenderTexture instancedTexture = new(parser.texture);
|
||||||
|
instancedTexture.name += $"_Copy_{instancedTexture.GetHashCode()}";
|
||||||
|
parser.texture = instancedTexture;
|
||||||
|
camera.targetTexture = instancedTexture;
|
||||||
|
tap._memoryTexture = instancedTexture;
|
||||||
|
|
||||||
|
ParentConstraint pc = tap.GetComponentInParent<ParentConstraint>(true);
|
||||||
|
if (pc && pc.sourceCount > 0)
|
||||||
|
{
|
||||||
|
tap._constraint = pc;
|
||||||
|
tap._dynamic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tap.SetOrificeMode(dpsOrifice.type);
|
||||||
|
tap.RecomputeMode();
|
||||||
|
tap.SyncLightState();
|
||||||
|
|
||||||
|
PlapPlapForAllMod.Logger.Msg(
|
||||||
|
$"PlapPlapTap created for orifice '{dpsOrifice.type}' using light '{dpsOrifice.basis.name}'. " +
|
||||||
|
$"Dynamic: {tap._dynamic}, Initial Mode: {tap._mode}");
|
||||||
|
|
||||||
|
tap._hasInitialized = true;
|
||||||
|
|
||||||
|
return tap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (!_hasInitialized) return;
|
||||||
|
RecomputeMode();
|
||||||
|
SyncLightState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (!_hasInitialized) return;
|
||||||
|
SyncLightState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (_memoryTexture)
|
||||||
|
{
|
||||||
|
Destroy(_memoryTexture);
|
||||||
|
_memoryTexture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!_dynamic || !_constraint) return;
|
||||||
|
|
||||||
|
int top = -1;
|
||||||
|
float best = -1f;
|
||||||
|
|
||||||
|
int count = _constraint.sourceCount;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ConstraintSource src = _constraint.GetSource(i);
|
||||||
|
if (!src.sourceTransform) continue;
|
||||||
|
|
||||||
|
float w = src.weight;
|
||||||
|
if (w > best)
|
||||||
|
{
|
||||||
|
best = w;
|
||||||
|
top = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top != _activeSourceIndex)
|
||||||
|
{
|
||||||
|
_activeSourceIndex = top;
|
||||||
|
RecomputeMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncLightState()
|
||||||
|
{
|
||||||
|
Light light = _dpsOrifice.dpsLight;
|
||||||
|
if (!light) return;
|
||||||
|
|
||||||
|
bool on = light.isActiveAndEnabled;
|
||||||
|
if (_lastLightState == on) return;
|
||||||
|
_lastLightState = on;
|
||||||
|
|
||||||
|
_plapPlapObject.SetActive(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecomputeMode()
|
||||||
|
{
|
||||||
|
Transform basis = _dpsOrifice.dpsLight.transform;
|
||||||
|
|
||||||
|
if (_dynamic && _constraint && _activeSourceIndex >= 0)
|
||||||
|
{
|
||||||
|
ConstraintSource src = _constraint.GetSource(_activeSourceIndex);
|
||||||
|
if (src.sourceTransform)
|
||||||
|
basis = src.sourceTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
string basisName = basis.name;
|
||||||
|
int bestScore = int.MinValue;
|
||||||
|
PlapPlapAudioMode bestMode = PlapPlapAudioMode.Generic;
|
||||||
|
|
||||||
|
for (int r = 0; r < Rules.Length; r++)
|
||||||
|
{
|
||||||
|
ref readonly Rule rule = ref Rules[r];
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
if (rule.HintBone.HasValue && _animator)
|
||||||
|
{
|
||||||
|
Transform bone = _animator.GetBoneTransform(rule.HintBone.Value);
|
||||||
|
if (bone && basis.IsChildOf(bone))
|
||||||
|
score += rule.BoneWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
string lowerName = basisName.ToLowerInvariant();
|
||||||
|
|
||||||
|
for (int k = 0; k < rule.Keywords.Length; k++)
|
||||||
|
{
|
||||||
|
string kw = rule.Keywords[k];
|
||||||
|
string lowerKw = kw.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (lowerName == lowerKw)
|
||||||
|
{
|
||||||
|
score += 8;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tokenMatch = false;
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i <= lowerName.Length; i++)
|
||||||
|
{
|
||||||
|
if (i == lowerName.Length || !char.IsLetter(lowerName[i]))
|
||||||
|
{
|
||||||
|
int len = i - start;
|
||||||
|
if (len == lowerKw.Length)
|
||||||
|
{
|
||||||
|
bool equal = true;
|
||||||
|
for (int c = 0; c < len; c++)
|
||||||
|
{
|
||||||
|
if (lowerName[start + c] != lowerKw[c])
|
||||||
|
{
|
||||||
|
equal = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (equal)
|
||||||
|
{
|
||||||
|
tokenMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenMatch)
|
||||||
|
{
|
||||||
|
score += lowerKw.Length == 1 ? 6 : 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowerKw.Length >= 4 && lowerName.Contains(lowerKw))
|
||||||
|
score += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score > bestScore)
|
||||||
|
{
|
||||||
|
bestScore = score;
|
||||||
|
bestMode = rule.Mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestMode == _mode) return;
|
||||||
|
_mode = bestMode;
|
||||||
|
|
||||||
|
SetAudioMode(_mode);
|
||||||
|
|
||||||
|
PlapPlapForAllMod.Logger.Msg($"PlapPlapTap applying mode {_mode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct Rule(PlapPlapAudioMode mode, HumanBodyBones? bone, int boneWeight, params string[] keywords)
|
||||||
|
{
|
||||||
|
public readonly PlapPlapAudioMode Mode = mode;
|
||||||
|
public readonly HumanBodyBones? HintBone = bone;
|
||||||
|
public readonly int BoneWeight = boneWeight;
|
||||||
|
public readonly string[] Keywords = keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interacting with Plap Plap */
|
||||||
|
|
||||||
|
public void SetAudioMode(PlapPlapAudioMode mode)
|
||||||
|
{
|
||||||
|
CVRAnimatorDriver animatorDriver = _plapPlapObject.GetComponent<CVRAnimatorDriver>();
|
||||||
|
animatorDriver.animatorParameter01 = (float)mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOrificeMode(DPSLightType mode)
|
||||||
|
{
|
||||||
|
CVRAnimatorDriver animatorDriver = _plapPlapObject.GetComponent<CVRAnimatorDriver>();
|
||||||
|
animatorDriver.animatorParameter02 = (float)mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
using ABI_RC.Core.InteractionSystem;
|
||||||
|
using ABI_RC.Core.InteractionSystem.Base;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.PlapPlapForAll;
|
||||||
|
|
||||||
|
public class StopHighlightPropagation : Pickupable
|
||||||
|
{
|
||||||
|
public override void OnGrab(InteractionContext context, Vector3 grabPoint)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDrop(InteractionContext context)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUseDown(InteractionContext context)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUseUp(InteractionContext context)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FlingTowardsTarget(ControllerRay controllerRay)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanPickup { get; }
|
||||||
|
public override bool DisallowTheft { get; }
|
||||||
|
public override float MaxGrabDistance { get; }
|
||||||
|
public override float MaxPushDistance { get; }
|
||||||
|
public override bool IsAutoHold { get; }
|
||||||
|
public override bool IsObjectRotationAllowed { get; }
|
||||||
|
public override bool IsObjectPushPullAllowed { get; }
|
||||||
|
public override bool IsTelepathicGrabAllowed { get; }
|
||||||
|
public override bool IsObjectUseAllowed { get; }
|
||||||
|
}
|
||||||
124
.Experimental/PlapPlapForAll/Main.cs
Normal file
124
.Experimental/PlapPlapForAll/Main.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
using ABI_RC.Core;
|
||||||
|
using ABI_RC.Core.Networking.IO.Social;
|
||||||
|
using MelonLoader;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Systems.GameEventSystem;
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Animations;
|
||||||
|
|
||||||
|
namespace NAK.PlapPlapForAll;
|
||||||
|
|
||||||
|
public class PlapPlapForAllMod : MelonMod
|
||||||
|
{
|
||||||
|
public static MelonLogger.Instance Logger;
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
Logger = LoggerInstance;
|
||||||
|
CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(OnPlayerSetupStart);
|
||||||
|
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(OnLocalAvatarLoaded);
|
||||||
|
CVRGameEventSystem.Avatar.OnRemoteAvatarLoad.AddListener(OnRemoteAvatarLoaded);
|
||||||
|
LoadAssetBundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnPlayerSetupStart()
|
||||||
|
{
|
||||||
|
PlapPlapPrefab.SetActive(false);
|
||||||
|
|
||||||
|
// Remove ParentConstraint so we can reparent later
|
||||||
|
ParentConstraint parentConstraint = PlapPlapPrefab.GetComponent<ParentConstraint>();
|
||||||
|
if (parentConstraint) UnityEngine.Object.DestroyImmediate(parentConstraint);
|
||||||
|
|
||||||
|
// Remove lights to avoid interfering with avatar lights
|
||||||
|
Light[] lights = PlapPlapPrefab.GetComponentsInChildren<Light>(true);
|
||||||
|
foreach (Light light in lights) UnityEngine.Object.DestroyImmediate(light);
|
||||||
|
|
||||||
|
// Register the audio sources underneath to the Avatar mixer group
|
||||||
|
AudioSource[] audioSources = PlapPlapPrefab.GetComponentsInChildren<AudioSource>(true);
|
||||||
|
foreach (AudioSource audioSource in audioSources) audioSource.outputAudioMixerGroup = RootLogic.Instance.avatarSfx;
|
||||||
|
|
||||||
|
// Add StopHighlightPropagation to prevent plap plap from being highlighted
|
||||||
|
StopHighlightPropagation stopHighlight = PlapPlapPrefab.AddComponent<StopHighlightPropagation>();
|
||||||
|
stopHighlight.enabled = false; // marker only
|
||||||
|
|
||||||
|
Logger.Msg("Patched PlapPlap prefab!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnLocalAvatarLoaded(CVRAvatar avatar)
|
||||||
|
=> OnAvatarLoaded(PlayerSetup.Instance, avatar.gameObject);
|
||||||
|
private static void OnRemoteAvatarLoaded(CVRPlayerEntity playerEntity, CVRAvatar avatar)
|
||||||
|
=> OnAvatarLoaded(playerEntity.PuppetMaster, avatar.gameObject);
|
||||||
|
|
||||||
|
private static void OnAvatarLoaded(PlayerBase player, GameObject avatarObject)
|
||||||
|
{
|
||||||
|
// Enforcing friends with benefits
|
||||||
|
if (!Friends.FriendsWith(player.PlayerId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Scan for DPS setups
|
||||||
|
if (!DPS.ScanForDPS(avatarObject, out List<DPSOrifice> dpsOrifices, out bool foundPenetrator))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If no penetrator found, attempt to find one via TPS
|
||||||
|
if (!foundPenetrator) DPS.AttemptTPSHack(avatarObject);
|
||||||
|
|
||||||
|
// Setup PlapPlap for each found orifice
|
||||||
|
if (dpsOrifices.Count != 0)
|
||||||
|
{
|
||||||
|
// Log found orifices
|
||||||
|
Logger.Msg($"Found {dpsOrifices.Count} DPS orifices on avatar '{avatarObject.name}' for player '{player.PlayerUsername}':");
|
||||||
|
foreach (DPSOrifice dpsOrifice in dpsOrifices) Logger.Msg($"- Orifice Type: {dpsOrifice.type}, DPS Light: {dpsOrifice.dpsLight.name}, Normal Light: {(dpsOrifice.normalLight != null ? dpsOrifice.normalLight.name : "None")}");
|
||||||
|
|
||||||
|
// Configure PlapPlap for each orifice
|
||||||
|
Animator avatarAnimator = player.Animator;
|
||||||
|
foreach (DPSOrifice dpsOrifice in dpsOrifices)
|
||||||
|
{
|
||||||
|
// Skip if this is already a plap plap setup
|
||||||
|
if (PlapPlapTap.IsBuiltInPlapPlapSetup(dpsOrifice))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PlapPlapTap.CreateFromOrifice(
|
||||||
|
dpsOrifice,
|
||||||
|
avatarAnimator,
|
||||||
|
PlapPlapPrefab
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Asset Bundle Loading */
|
||||||
|
|
||||||
|
private const string PlapPlapAssetsName = "PlapPlapForAll.Resources.plap plap.assets";
|
||||||
|
private const string PlapPlapPrefabName = "Assets/Noachi/Plap Plap/plap plap.prefab";
|
||||||
|
|
||||||
|
private static GameObject PlapPlapPrefab;
|
||||||
|
|
||||||
|
private void LoadAssetBundle()
|
||||||
|
{
|
||||||
|
LoggerInstance.Msg($"Loading required asset bundle...");
|
||||||
|
using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(PlapPlapAssetsName);
|
||||||
|
using MemoryStream memoryStream = new();
|
||||||
|
if (resourceStream == null) {
|
||||||
|
LoggerInstance.Error($"Failed to load {PlapPlapAssetsName}!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceStream.CopyTo(memoryStream);
|
||||||
|
AssetBundle assetBundle = AssetBundle.LoadFromStream(memoryStream);
|
||||||
|
if (assetBundle == null) {
|
||||||
|
LoggerInstance.Error($"Failed to load {PlapPlapAssetsName}! Asset bundle is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlapPlapPrefab = assetBundle.LoadAsset<GameObject>(PlapPlapPrefabName);
|
||||||
|
if (PlapPlapPrefab == null) {
|
||||||
|
LoggerInstance.Error($"Failed to load {PlapPlapPrefabName}! Prefab is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PlapPlapPrefab.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||||
|
LoggerInstance.Msg($"Loaded {PlapPlapPrefabName}!");
|
||||||
|
|
||||||
|
LoggerInstance.Msg("Asset bundle successfully loaded!");
|
||||||
|
}
|
||||||
|
}
|
||||||
7
.Experimental/PlapPlapForAll/PlapPlapForAll.csproj
Normal file
7
.Experimental/PlapPlapForAll/PlapPlapForAll.csproj
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Resources\plap plap.assets" />
|
||||||
|
<EmbeddedResource Include="Resources\plap plap.assets" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
32
.Experimental/PlapPlapForAll/Properties/AssemblyInfo.cs
Normal file
32
.Experimental/PlapPlapForAll/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
using NAK.PlapPlapForAll.Properties;
|
||||||
|
using MelonLoader;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.PlapPlapForAll))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.PlapPlapForAll))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.PlapPlapForAll.PlapPlapForAllMod),
|
||||||
|
nameof(NAK.PlapPlapForAll),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PlapPlapForAll"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||||
|
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||||
|
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||||
|
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||||
|
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||||
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
|
namespace NAK.PlapPlapForAll.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
public const string Author = "NotAKidoS, Noachi";
|
||||||
|
}
|
||||||
16
.Experimental/PlapPlapForAll/README.md
Normal file
16
.Experimental/PlapPlapForAll/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# PlapPlapForAll
|
||||||
|
|
||||||
|
Adds Noach's PlapPlap prefab to any detected DPS setups on avatars.
|
||||||
|
|
||||||
|
Will also fix existing penetrator setups which are missing the tip light.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here is the block of text where I tell you this mod is not affiliated or endorsed by ~~~~ABI.
|
||||||
|
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||||
|
|
||||||
|
> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive.
|
||||||
|
|
||||||
|
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||||
|
|
||||||
|
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
||||||
BIN
.Experimental/PlapPlapForAll/Resources/plap plap.assets
Normal file
BIN
.Experimental/PlapPlapForAll/Resources/plap plap.assets
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue