mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-04 23:39:22 +00:00
Compare commits
No commits in common. "6249696efaa00151708201b07259ea5cd5284e4d" and "5e822cec8d5c9faeb8ccb1c98e044be32d30f1da" have entirely different histories.
6249696efa
...
5e822cec8d
645 changed files with 3798 additions and 5463 deletions
|
@ -1,229 +0,0 @@
|
|||
using ABI.CCK.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public partial class AvatarClone
|
||||
{
|
||||
#region Exclusions
|
||||
|
||||
private FPRExclusion[] _exclusions;
|
||||
|
||||
private void AddExclusionToHeadIfNeeded()
|
||||
{
|
||||
if (!TryGetComponent(out Animator animator)
|
||||
|| !animator.isHuman
|
||||
|| !animator.avatar
|
||||
|| !animator.avatar.isValid)
|
||||
return;
|
||||
|
||||
Transform head = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
if (!head)
|
||||
return;
|
||||
|
||||
GameObject headGo = head.gameObject;
|
||||
if (headGo.TryGetComponent(out FPRExclusion exclusion))
|
||||
return;
|
||||
|
||||
exclusion = headGo.AddComponent<FPRExclusion>();
|
||||
exclusion.target = head;
|
||||
exclusion.isShown = false;
|
||||
}
|
||||
|
||||
private void InitializeExclusions()
|
||||
{
|
||||
_exclusions = GetComponentsInChildren<FPRExclusion>(true);
|
||||
var exclusionRoots = new Dictionary<Transform, AvatarCloneExclusion>(_exclusions.Length);
|
||||
|
||||
// **1. Precompute Exclusions**
|
||||
foreach (FPRExclusion exclusion in _exclusions)
|
||||
{
|
||||
Transform target = exclusion.target ??= exclusion.transform;
|
||||
if (exclusionRoots.ContainsKey(target) || !target.gameObject.scene.IsValid())
|
||||
continue;
|
||||
|
||||
AvatarCloneExclusion behaviour = new AvatarCloneExclusion(this, target);
|
||||
exclusion.behaviour = behaviour;
|
||||
exclusionRoots.Add(target, behaviour);
|
||||
}
|
||||
|
||||
// Process Exclusion Transforms
|
||||
Renderer ourRenderer;
|
||||
|
||||
void ProcessTransformHierarchy(Transform current, Transform root, AvatarCloneExclusion behaviour)
|
||||
{
|
||||
if (exclusionRoots.ContainsKey(current) && current != root) return;
|
||||
|
||||
behaviour.affectedTransforms.Add(current);
|
||||
if (current.TryGetComponent(out ourRenderer))
|
||||
behaviour.affectedRenderers.Add(ourRenderer);
|
||||
|
||||
for (int i = 0; i < current.childCount; i++)
|
||||
{
|
||||
Transform child = current.GetChild(i);
|
||||
if (!exclusionRoots.ContainsKey(child))
|
||||
ProcessTransformHierarchy(child, root, behaviour);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entry in exclusionRoots)
|
||||
{
|
||||
Transform rootTransform = entry.Key;
|
||||
AvatarCloneExclusion behaviour = entry.Value;
|
||||
ProcessTransformHierarchy(rootTransform, rootTransform, behaviour);
|
||||
behaviour.affectedTransformSet = new HashSet<Transform>(behaviour.affectedTransforms);
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// **OPTIMIZED EXCLUSION BONE MAPPING**
|
||||
// ------------------------------
|
||||
|
||||
Dictionary<Transform, AvatarCloneExclusion>.ValueCollection exclusionBehaviours = exclusionRoots.Values;
|
||||
int skinnedCount = _skinnedClones.Count;
|
||||
|
||||
// **2. Precompute Bone-to-Exclusion Mapping**
|
||||
int estimatedBoneCount = skinnedCount * 20; // Estimated bones per skinned mesh
|
||||
var boneToExclusion = new Dictionary<Transform, List<AvatarCloneExclusion>>(estimatedBoneCount);
|
||||
|
||||
foreach (AvatarCloneExclusion behaviour in exclusionBehaviours)
|
||||
{
|
||||
foreach (Transform bone in behaviour.affectedTransformSet)
|
||||
{
|
||||
if (!boneToExclusion.TryGetValue(bone, out var list))
|
||||
{
|
||||
list = new List<AvatarCloneExclusion>(2);
|
||||
boneToExclusion[bone] = list;
|
||||
}
|
||||
list.Add(behaviour);
|
||||
}
|
||||
}
|
||||
|
||||
// **3. Process Skinned Mesh Renderers**
|
||||
for (int s = 0; s < skinnedCount; s++)
|
||||
{
|
||||
SkinnedMeshRenderer source = _skinnedRenderers[s];
|
||||
var bones = source.bones; // Cache bones array
|
||||
|
||||
SkinnedMeshRenderer smr = _skinnedClones[s];
|
||||
int boneCount = bones.Length;
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
{
|
||||
Transform bone = bones[i];
|
||||
|
||||
// **Skip if the bone isn't mapped to exclusions**
|
||||
if (!bone // Skip null bones
|
||||
|| !boneToExclusion.TryGetValue(bone, out var behaviours))
|
||||
continue;
|
||||
|
||||
// **Avoid redundant dictionary lookups**
|
||||
for (int j = 0; j < behaviours.Count; j++)
|
||||
{
|
||||
AvatarCloneExclusion behaviour = behaviours[j];
|
||||
|
||||
if (!behaviour.skinnedToBoneIndex.TryGetValue(smr, out var indices))
|
||||
{
|
||||
indices = new List<int>(4);
|
||||
behaviour.skinnedToBoneIndex[smr] = indices;
|
||||
}
|
||||
|
||||
indices.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyInitialExclusionState();
|
||||
}
|
||||
|
||||
public void ApplyInitialExclusionState()
|
||||
{
|
||||
foreach (FPRExclusion exclusion in _exclusions)
|
||||
{
|
||||
exclusion._wasShown = exclusion.isShown;
|
||||
if (!exclusion.isShown) exclusion.UpdateExclusions();
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleExclusionUpdate(AvatarCloneExclusion exclusion, bool isShown)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_UpdateExclusions.Begin();
|
||||
#endif
|
||||
|
||||
// **1. Update Renderer Visibility**
|
||||
foreach (Renderer renderer in exclusion.affectedRenderers)
|
||||
{
|
||||
if (renderer is SkinnedMeshRenderer skinned)
|
||||
{
|
||||
int index = _skinnedRenderers.IndexOf(skinned);
|
||||
if (index >= 0) _skinnedClones[index].gameObject.SetActive(isShown);
|
||||
}
|
||||
else if (renderer is MeshRenderer mesh)
|
||||
{
|
||||
int index = _meshRenderers.IndexOf(mesh);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
_meshClones[index].gameObject.SetActive(isShown);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other renderer (never cloned) - update shadow casting state
|
||||
_sourceShouldBeHiddenFromFPR[index] = !isShown; // When hidden, use for shadows
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (renderer)
|
||||
{
|
||||
int index = _otherRenderers.IndexOf(renderer);
|
||||
if (index >= 0)
|
||||
{
|
||||
int shadowIndex = index + (Setting_CloneMeshRenderers ? _meshRenderers.Count : 0);
|
||||
_sourceShouldBeHiddenFromFPR[shadowIndex] = !isShown; // When hidden, use for shadows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **2. Update Bone References in Skinned Mesh Renderers**
|
||||
UpdateSkinnedMeshBones(exclusion, exclusion._shrinkBone, isShown);
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_UpdateExclusions.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void UpdateSkinnedMeshBones(AvatarCloneExclusion exclusion, Transform shrinkBone, bool isShown)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_HandleBoneUpdates.Begin();
|
||||
#endif
|
||||
|
||||
foreach (var smrEntry in exclusion.skinnedToBoneIndex)
|
||||
{
|
||||
SkinnedMeshRenderer smr = smrEntry.Key;
|
||||
var indices = smrEntry.Value;
|
||||
bool needsUpdate = false;
|
||||
|
||||
var parentBones = smr.transform.parent.GetComponent<SkinnedMeshRenderer>().bones;
|
||||
var cloneBones = smr.bones;
|
||||
Array.Resize(ref cloneBones, parentBones.Length);
|
||||
|
||||
// Only modify our bones, other exclusions may have modified others
|
||||
for (int i = 0; i < indices.Count; i++)
|
||||
{
|
||||
int index = indices[i];
|
||||
if (!isShown) cloneBones[index] = shrinkBone;
|
||||
else cloneBones[index] = parentBones[index];
|
||||
needsUpdate = true;
|
||||
}
|
||||
if (needsUpdate) smr.bones = cloneBones;
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_HandleBoneUpdates.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion Exclusions
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
using ABI_RC.Core.Player.ShadowClone;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public partial class AvatarClone
|
||||
{
|
||||
#region Initialization
|
||||
|
||||
private void InitializeCollections()
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_InitializeData.Begin();
|
||||
#endif
|
||||
|
||||
// Initialize source collections
|
||||
_skinnedRenderers = new List<SkinnedMeshRenderer>();
|
||||
_blendShapeWeights = new List<List<float>>();
|
||||
|
||||
_meshRenderers = new List<MeshRenderer>();
|
||||
_meshFilters = new List<MeshFilter>();
|
||||
|
||||
_otherRenderers = new List<Renderer>();
|
||||
|
||||
// Initialize clone collections
|
||||
_skinnedClones = new List<SkinnedMeshRenderer>();
|
||||
_skinnedCloneMaterials = new List<Material[]>();
|
||||
_skinnedCloneCullingMaterials = new List<Material[]>();
|
||||
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
_meshClones = new List<MeshRenderer>();
|
||||
_meshCloneFilters = new List<MeshFilter>();
|
||||
_meshCloneMaterials = new List<Material[]>();
|
||||
_meshCloneCullingMaterials = new List<Material[]>();
|
||||
}
|
||||
|
||||
// Initialize shared resources
|
||||
_materialWorkingList = new List<Material>();
|
||||
_propertyBlock = new MaterialPropertyBlock();
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_InitializeData.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CollectRenderers()
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_InitializeData.Begin();
|
||||
#endif
|
||||
|
||||
var renderers = GetComponentsInChildren<Renderer>(true);
|
||||
var currentIndex = 0;
|
||||
var nonCloned = 0;
|
||||
|
||||
// Single pass: directly categorize renderers
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
switch (renderer)
|
||||
{
|
||||
case SkinnedMeshRenderer skinned when skinned.sharedMesh != null:
|
||||
AddSkinnedRenderer(skinned);
|
||||
currentIndex++;
|
||||
break;
|
||||
|
||||
case MeshRenderer mesh:
|
||||
MeshFilter filter = mesh.GetComponent<MeshFilter>();
|
||||
if (filter != null && filter.sharedMesh != null)
|
||||
{
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
AddMeshRenderer(mesh, filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddMeshRenderer(mesh, filter);
|
||||
nonCloned++;
|
||||
}
|
||||
currentIndex++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
AddOtherRenderer(renderer);
|
||||
currentIndex++;
|
||||
nonCloned++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_rendererActiveStates = new bool[currentIndex];
|
||||
_originalShadowCastingMode = new ShadowCastingMode[currentIndex];
|
||||
_sourceShouldBeHiddenFromFPR = new bool[nonCloned];
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_InitializeData.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void AddSkinnedRenderer(SkinnedMeshRenderer renderer)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_AddRenderer.Begin();
|
||||
#endif
|
||||
|
||||
_skinnedRenderers.Add(renderer);
|
||||
|
||||
// Clone materials array for clone renderer
|
||||
var materials = renderer.sharedMaterials;
|
||||
var cloneMaterials = new Material[materials.Length];
|
||||
for (int i = 0; i < materials.Length; i++) cloneMaterials[i] = materials[i];
|
||||
_skinnedCloneMaterials.Add(cloneMaterials);
|
||||
|
||||
// Cache culling materials
|
||||
var cullingMaterialArray = new Material[materials.Length];
|
||||
#if !UNITY_EDITOR
|
||||
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = ShadowCloneUtils.cullingMaterial;
|
||||
#else
|
||||
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = cullingMaterial;
|
||||
#endif
|
||||
_skinnedCloneCullingMaterials.Add(cullingMaterialArray);
|
||||
|
||||
// Cache blend shape weights
|
||||
var weights = new List<float>(renderer.sharedMesh.blendShapeCount);
|
||||
for (int i = 0; i < renderer.sharedMesh.blendShapeCount; i++) weights.Add(0f);
|
||||
_blendShapeWeights.Add(weights);
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_AddRenderer.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void AddMeshRenderer(MeshRenderer renderer, MeshFilter filter)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_AddRenderer.Begin();
|
||||
#endif
|
||||
|
||||
_meshRenderers.Add(renderer);
|
||||
_meshFilters.Add(filter);
|
||||
|
||||
if (!Setting_CloneMeshRenderers) return;
|
||||
|
||||
// Clone materials array for clone renderer
|
||||
var materials = renderer.sharedMaterials;
|
||||
var cloneMaterials = new Material[materials.Length];
|
||||
for (int i = 0; i < materials.Length; i++) cloneMaterials[i] = materials[i];
|
||||
_meshCloneMaterials.Add(cloneMaterials);
|
||||
|
||||
// Cache culling materials
|
||||
var cullingMaterialArray = new Material[materials.Length];
|
||||
#if !UNITY_EDITOR
|
||||
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = ShadowCloneUtils.cullingMaterial;
|
||||
#else
|
||||
for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = cullingMaterial;
|
||||
#endif
|
||||
_meshCloneCullingMaterials.Add(cullingMaterialArray);
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_AddRenderer.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void AddOtherRenderer(Renderer renderer)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_AddRenderer.Begin();
|
||||
#endif
|
||||
_otherRenderers.Add(renderer);
|
||||
#if ENABLE_PROFILER
|
||||
s_AddRenderer.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CreateClones()
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_InitializeData.Begin();
|
||||
#endif
|
||||
|
||||
// Always create skinned mesh clones
|
||||
int skinnedCount = _skinnedRenderers.Count;
|
||||
for (int i = 0; i < skinnedCount; i++)
|
||||
{
|
||||
CreateSkinnedClone(i);
|
||||
}
|
||||
|
||||
// Optionally create mesh clones
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
int meshCount = _meshRenderers.Count;
|
||||
for (int i = 0; i < meshCount; i++)
|
||||
{
|
||||
CreateMeshClone(i);
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_InitializeData.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CreateSkinnedClone(int index)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_CreateClone.Begin();
|
||||
#endif
|
||||
|
||||
SkinnedMeshRenderer source = _skinnedRenderers[index];
|
||||
|
||||
GameObject clone = new(source.name + "_Clone")
|
||||
{
|
||||
layer = CLONE_LAYER
|
||||
};
|
||||
|
||||
clone.transform.SetParent(source.transform, false);
|
||||
|
||||
SkinnedMeshRenderer cloneRenderer = clone.AddComponent<SkinnedMeshRenderer>();
|
||||
|
||||
// Basic setup
|
||||
cloneRenderer.sharedMaterials = _skinnedCloneMaterials[index];
|
||||
cloneRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
cloneRenderer.probeAnchor = source.probeAnchor;
|
||||
cloneRenderer.sharedMesh = source.sharedMesh;
|
||||
cloneRenderer.rootBone = source.rootBone;
|
||||
cloneRenderer.bones = source.bones;
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
cloneRenderer.localBounds = new Bounds(source.localBounds.center, source.localBounds.size * 2f);
|
||||
#endif
|
||||
|
||||
// Quality settings
|
||||
cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||
cloneRenderer.allowOcclusionWhenDynamic = false;
|
||||
cloneRenderer.updateWhenOffscreen = false;
|
||||
cloneRenderer.skinnedMotionVectors = false;
|
||||
cloneRenderer.forceMatrixRecalculationPerRender = false;
|
||||
cloneRenderer.quality = SkinQuality.Bone4;
|
||||
|
||||
source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||
source.allowOcclusionWhenDynamic = false;
|
||||
source.updateWhenOffscreen = false;
|
||||
source.skinnedMotionVectors = false;
|
||||
source.forceMatrixRecalculationPerRender = false;
|
||||
source.quality = SkinQuality.Bone4;
|
||||
|
||||
// Add to clone list
|
||||
_skinnedClones.Add(cloneRenderer);
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_CreateClone.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CreateMeshClone(int index)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_CreateClone.Begin();
|
||||
#endif
|
||||
|
||||
MeshRenderer source = _meshRenderers[index];
|
||||
MeshFilter sourceFilter = _meshFilters[index];
|
||||
|
||||
GameObject clone = new(source.name + "_Clone")
|
||||
{
|
||||
layer = CLONE_LAYER
|
||||
};
|
||||
|
||||
clone.transform.SetParent(source.transform, false);
|
||||
|
||||
MeshRenderer cloneRenderer = clone.AddComponent<MeshRenderer>();
|
||||
MeshFilter cloneFilter = clone.AddComponent<MeshFilter>();
|
||||
|
||||
// Basic setup
|
||||
cloneRenderer.sharedMaterials = _meshCloneMaterials[index];
|
||||
cloneRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
cloneRenderer.probeAnchor = source.probeAnchor;
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
cloneRenderer.localBounds = new Bounds(source.localBounds.center, source.localBounds.size * 2f);
|
||||
#endif
|
||||
|
||||
cloneFilter.sharedMesh = sourceFilter.sharedMesh;
|
||||
|
||||
// Quality settings
|
||||
cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||
cloneRenderer.allowOcclusionWhenDynamic = false;
|
||||
|
||||
source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||||
source.allowOcclusionWhenDynamic = false;
|
||||
|
||||
// Add to clone lists
|
||||
_meshClones.Add(cloneRenderer);
|
||||
_meshCloneFilters.Add(cloneFilter);
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_CreateClone.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion Initialization
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public partial class AvatarClone
|
||||
{
|
||||
#region Render State Management
|
||||
|
||||
private void MyOnPreCull(Camera cam)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Scene & Preview cameras are not needed
|
||||
if (cam.cameraType != CameraType.Game)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_PreCullUpdate.Begin();
|
||||
#endif
|
||||
|
||||
bool isOurUiCamera = IsUIInternalCamera(cam);
|
||||
bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam);
|
||||
bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam);
|
||||
|
||||
bool rendersBothPlayerLayers = rendersOurPlayerLayer && rendersOurCloneLayer;
|
||||
|
||||
// Handle shadow casting when camera renders both layers
|
||||
if (!_sourcesSetForShadowCasting
|
||||
&& rendersBothPlayerLayers)
|
||||
{
|
||||
ConfigureSourceShadowCasting(true);
|
||||
_sourcesSetForShadowCasting = true;
|
||||
}
|
||||
else if (_sourcesSetForShadowCasting && !rendersBothPlayerLayers)
|
||||
{
|
||||
ConfigureSourceShadowCasting(false);
|
||||
_sourcesSetForShadowCasting = false;
|
||||
}
|
||||
|
||||
// Handle UI culling for clone layer
|
||||
if (!_clonesSetForUiCulling
|
||||
&& isOurUiCamera && rendersOurCloneLayer)
|
||||
{
|
||||
ConfigureCloneUICulling(true);
|
||||
_clonesSetForUiCulling = true;
|
||||
}
|
||||
else if (_clonesSetForUiCulling)
|
||||
{
|
||||
ConfigureCloneUICulling(false);
|
||||
_clonesSetForUiCulling = false;
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_PreCullUpdate.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ConfigureSourceShadowCasting(bool setSourcesToShadowCast)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_ConfigureShadowCasting.Begin();
|
||||
#endif
|
||||
|
||||
int currentIndex = 0;
|
||||
int shadowArrayIndex = 0;
|
||||
|
||||
// Handle skinned mesh renderers (always have clones)
|
||||
int skinnedCount = _skinnedRenderers.Count;
|
||||
for (int i = 0; i < skinnedCount; i++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex]) continue;
|
||||
|
||||
SkinnedMeshRenderer source = _skinnedRenderers[i];
|
||||
|
||||
if (setSourcesToShadowCast)
|
||||
{
|
||||
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
|
||||
if (originalMode == ShadowCastingMode.Off)
|
||||
source.forceRenderingOff = true;
|
||||
else
|
||||
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
|
||||
source.forceRenderingOff = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle mesh renderers based on clone setting
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
int meshCount = _meshRenderers.Count;
|
||||
for (int i = 0; i < meshCount; i++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex]) continue;
|
||||
|
||||
MeshRenderer source = _meshRenderers[i];
|
||||
|
||||
if (setSourcesToShadowCast)
|
||||
{
|
||||
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
|
||||
if (originalMode == ShadowCastingMode.Off)
|
||||
source.forceRenderingOff = true;
|
||||
else
|
||||
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
|
||||
source.forceRenderingOff = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// When not cloned, mesh renderers use the shadow casting array
|
||||
int meshCount = _meshRenderers.Count;
|
||||
for (int i = 0; i < meshCount; i++, shadowArrayIndex++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex]) continue;
|
||||
if (!_sourceShouldBeHiddenFromFPR[shadowArrayIndex]) continue;
|
||||
|
||||
MeshRenderer source = _meshRenderers[i];
|
||||
|
||||
if (setSourcesToShadowCast)
|
||||
{
|
||||
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
|
||||
if (originalMode == ShadowCastingMode.Off)
|
||||
source.forceRenderingOff = true;
|
||||
else
|
||||
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
|
||||
source.forceRenderingOff = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle other renderers (never cloned)
|
||||
int otherCount = _otherRenderers.Count;
|
||||
for (int i = 0; i < otherCount; i++, shadowArrayIndex++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex]) continue;
|
||||
if (!_sourceShouldBeHiddenFromFPR[shadowArrayIndex]) continue;
|
||||
|
||||
Renderer source = _otherRenderers[i];
|
||||
|
||||
if (setSourcesToShadowCast)
|
||||
{
|
||||
ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode;
|
||||
if (originalMode == ShadowCastingMode.Off)
|
||||
source.forceRenderingOff = true;
|
||||
else
|
||||
source.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
source.shadowCastingMode = _originalShadowCastingMode[currentIndex];
|
||||
source.forceRenderingOff = false;
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_ConfigureShadowCasting.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ConfigureCloneUICulling(bool enableCulling)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_ConfigureUICulling.Begin();
|
||||
#endif
|
||||
|
||||
// Set the materials to our culling materials
|
||||
int currentIndex = 0;
|
||||
|
||||
int skinnedCount = _skinnedRenderers.Count;
|
||||
for (int i = 0; i < skinnedCount; i++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex])
|
||||
continue;
|
||||
|
||||
_skinnedClones[i].sharedMaterials = enableCulling ?
|
||||
_skinnedCloneCullingMaterials[i] :
|
||||
_skinnedCloneMaterials[i];
|
||||
}
|
||||
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
int meshCount = _meshRenderers.Count;
|
||||
for (int i = 0; i < meshCount; i++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex])
|
||||
continue;
|
||||
|
||||
_meshClones[i].sharedMaterials = enableCulling ?
|
||||
_meshCloneCullingMaterials[i] :
|
||||
_meshCloneMaterials[i];
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_ConfigureUICulling.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion Render State Management
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public partial class AvatarClone
|
||||
{
|
||||
#region State Syncing
|
||||
|
||||
private void SyncEnabledState()
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_CopyEnabledState.Begin();
|
||||
#endif
|
||||
|
||||
int currentIndex = 0;
|
||||
|
||||
// Update skinned mesh renderers
|
||||
int skinnedCount = _skinnedRenderers.Count;
|
||||
for (int i = 0; i < skinnedCount; i++, currentIndex++)
|
||||
{
|
||||
SkinnedMeshRenderer source = _skinnedRenderers[i];
|
||||
_skinnedClones[i].enabled = _rendererActiveStates[currentIndex] = IsRendererActive(source);
|
||||
}
|
||||
|
||||
// Update mesh renderers
|
||||
int meshCount = _meshRenderers.Count;
|
||||
for (int i = 0; i < meshCount; i++, currentIndex++)
|
||||
{
|
||||
MeshRenderer source = _meshRenderers[i];
|
||||
if (Setting_CloneMeshRenderers) _meshClones[i].enabled = _rendererActiveStates[currentIndex] = IsRendererActive(source);
|
||||
else _rendererActiveStates[currentIndex] = IsRendererActive(source);
|
||||
}
|
||||
|
||||
// Update other renderers
|
||||
int otherCount = _otherRenderers.Count;
|
||||
for (int i = 0; i < otherCount; i++, currentIndex++)
|
||||
{
|
||||
Renderer source = _otherRenderers[i];
|
||||
_rendererActiveStates[currentIndex] = IsRendererActive(source);
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_CopyEnabledState.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SyncMaterials()
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_CopyMaterials.Begin();
|
||||
#endif
|
||||
int currentIndex = 0;
|
||||
|
||||
// Sync skinned mesh materials
|
||||
int skinnedCount = _skinnedRenderers.Count;
|
||||
for (int i = 0; i < skinnedCount; i++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex])
|
||||
continue;
|
||||
|
||||
CopyMaterialsAndProperties(
|
||||
_skinnedRenderers[i],
|
||||
_skinnedClones[i],
|
||||
_propertyBlock,
|
||||
_materialWorkingList,
|
||||
_skinnedCloneMaterials[i]);
|
||||
}
|
||||
|
||||
// Sync mesh materials if enabled
|
||||
if (Setting_CloneMeshRenderers)
|
||||
{
|
||||
int meshCount = _meshRenderers.Count;
|
||||
for (int i = 0; i < meshCount; i++, currentIndex++)
|
||||
{
|
||||
if (!_rendererActiveStates[currentIndex])
|
||||
continue;
|
||||
|
||||
CopyMaterialsAndProperties(
|
||||
_meshRenderers[i],
|
||||
_meshClones[i],
|
||||
_propertyBlock,
|
||||
_materialWorkingList,
|
||||
_meshCloneMaterials[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_CopyMaterials.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SyncBlendShapes()
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
s_CopyBlendShapes.Begin();
|
||||
#endif
|
||||
|
||||
int skinnedCount = _skinnedRenderers.Count;
|
||||
for (int i = 0; i < skinnedCount; i++)
|
||||
{
|
||||
SkinnedMeshRenderer source = _skinnedRenderers[i];
|
||||
if (!_rendererActiveStates[i])
|
||||
continue;
|
||||
|
||||
CopyBlendShapes(
|
||||
source,
|
||||
_skinnedClones[i],
|
||||
_blendShapeWeights[i]);
|
||||
}
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
s_CopyBlendShapes.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void CopyMaterialsAndProperties(
|
||||
Renderer source,
|
||||
Renderer clone,
|
||||
MaterialPropertyBlock propertyBlock,
|
||||
List<Material> workingList,
|
||||
Material[] cloneMaterials)
|
||||
{
|
||||
source.GetSharedMaterials(workingList);
|
||||
|
||||
int matCount = workingList.Count;
|
||||
bool hasChanged = false;
|
||||
|
||||
for (int i = 0; i < matCount; i++)
|
||||
{
|
||||
if (ReferenceEquals(workingList[i], cloneMaterials[i])) continue;
|
||||
cloneMaterials[i] = workingList[i];
|
||||
hasChanged = true;
|
||||
}
|
||||
if (hasChanged) clone.sharedMaterials = cloneMaterials;
|
||||
|
||||
source.GetPropertyBlock(propertyBlock);
|
||||
clone.SetPropertyBlock(propertyBlock);
|
||||
}
|
||||
|
||||
private static void CopyBlendShapes(
|
||||
SkinnedMeshRenderer source,
|
||||
SkinnedMeshRenderer clone,
|
||||
List<float> weights)
|
||||
{
|
||||
int weightCount = weights.Count;
|
||||
for (int i = 0; i < weightCount; i++)
|
||||
{
|
||||
float weight = source.GetBlendShapeWeight(i);
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (weight == weights[i]) continue; // Halves the work
|
||||
clone.SetBlendShapeWeight(i, weights[i] = weight);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion State Syncing
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using MagicaCloth;
|
||||
using MagicaCloth2;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public partial class AvatarClone
|
||||
{
|
||||
#region Utilities
|
||||
|
||||
private static bool IsRendererActive(Renderer renderer)
|
||||
=> renderer && renderer.enabled && renderer.gameObject.activeInHierarchy;
|
||||
|
||||
private static bool CameraRendersPlayerLocalLayer(Camera cam)
|
||||
=> (cam.cullingMask & (1 << LOCAL_LAYER)) != 0;
|
||||
|
||||
private static bool CameraRendersPlayerCloneLayer(Camera cam)
|
||||
=> (cam.cullingMask & (1 << CLONE_LAYER)) != 0;
|
||||
|
||||
private static bool IsUIInternalCamera(Camera cam)
|
||||
#if !UNITY_EDITOR
|
||||
=> cam == PlayerSetup.Instance.activeUiCam;
|
||||
#else
|
||||
=> cam.gameObject.layer == 15;
|
||||
#endif
|
||||
|
||||
#endregion Utilities
|
||||
|
||||
#region Magica Cloth Support
|
||||
|
||||
private void SetupMagicaClothSupport()
|
||||
{
|
||||
var magicaCloths1 = GetComponentsInChildren<BaseCloth>(true);
|
||||
foreach (BaseCloth magicaCloth1 in magicaCloths1)
|
||||
magicaCloth1.SetCullingMode(PhysicsTeam.TeamCullingMode.Off);
|
||||
|
||||
var magicaCloths2 = base.GetComponentsInChildren<MagicaCloth2.MagicaCloth>(true);
|
||||
foreach (MagicaCloth2.MagicaCloth magicaCloth2 in magicaCloths2)
|
||||
magicaCloth2.serializeData.cullingSettings.cameraCullingMode = CullingSettings.CameraCullingMode.AnimatorLinkage;
|
||||
}
|
||||
|
||||
public void OnMagicaClothMeshSwapped(Renderer render, Mesh newMesh)
|
||||
{
|
||||
switch (render)
|
||||
{
|
||||
case MeshRenderer mesh:
|
||||
{
|
||||
int index = _meshRenderers.IndexOf(mesh);
|
||||
if (index != -1) _meshCloneFilters[index].sharedMesh = newMesh;
|
||||
break;
|
||||
}
|
||||
case SkinnedMeshRenderer skinned:
|
||||
{
|
||||
int index = _skinnedRenderers.IndexOf(skinned);
|
||||
if (index != -1)
|
||||
{
|
||||
// Copy the mesh
|
||||
_skinnedClones[index].sharedMesh = newMesh;
|
||||
|
||||
// Copy appended bones if count is different
|
||||
var cloneBones = _skinnedClones[index].bones; // alloc
|
||||
var sourceBones = skinned.bones; // alloc
|
||||
|
||||
int cloneBoneCount = cloneBones.Length;
|
||||
int sourceBoneCount = sourceBones.Length;
|
||||
if (cloneBoneCount != sourceBoneCount)
|
||||
{
|
||||
// Copy the new bones only
|
||||
Array.Resize(ref cloneBones, sourceBoneCount);
|
||||
for (int i = cloneBoneCount; i < sourceBoneCount; i++) cloneBones[i] = sourceBones[i];
|
||||
_skinnedClones[index].bones = cloneBones;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Magica Cloth Support
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
using NAK.AvatarCloneTest;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public partial class AvatarClone : MonoBehaviour
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const int LOCAL_LAYER = 8;
|
||||
private const int CLONE_LAYER = 9;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Profiler Markers
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
private static readonly ProfilerMarker s_CopyEnabledState = new($"{nameof(AvatarClone)}.{nameof(SyncEnabledState)}");
|
||||
private static readonly ProfilerMarker s_CopyMaterials = new($"{nameof(AvatarClone)}.{nameof(CopyMaterialsAndProperties)}");
|
||||
private static readonly ProfilerMarker s_CopyBlendShapes = new($"{nameof(AvatarClone)}.{nameof(CopyBlendShapes)}");
|
||||
private static readonly ProfilerMarker s_InitializeData = new($"{nameof(AvatarClone)}.Initialize");
|
||||
private static readonly ProfilerMarker s_UpdateExclusions = new($"{nameof(AvatarClone)}.{nameof(HandleExclusionUpdate)}");
|
||||
private static readonly ProfilerMarker s_CollectExclusionData = new($"{nameof(AvatarClone)}.{nameof(CollectExclusionData)}");
|
||||
private static readonly ProfilerMarker s_HandleBoneUpdates = new($"{nameof(AvatarClone)}.{nameof(UpdateSkinnedMeshBones)}");
|
||||
private static readonly ProfilerMarker s_PreCullUpdate = new($"{nameof(AvatarClone)}.{nameof(MyOnPreCull)}");
|
||||
private static readonly ProfilerMarker s_ConfigureShadowCasting = new($"{nameof(AvatarClone)}.{nameof(ConfigureSourceShadowCasting)}");
|
||||
private static readonly ProfilerMarker s_ConfigureUICulling = new($"{nameof(AvatarClone)}.{nameof(ConfigureCloneUICulling)}");
|
||||
private static readonly ProfilerMarker s_AddRenderer = new($"{nameof(AvatarClone)}.AddRenderer");
|
||||
private static readonly ProfilerMarker s_CreateClone = new($"{nameof(AvatarClone)}.CreateClone");
|
||||
#endif
|
||||
|
||||
#endregion Profiler Markers
|
||||
|
||||
#region Settings
|
||||
|
||||
public bool Setting_CloneMeshRenderers;
|
||||
public bool Setting_CopyMaterials = true;
|
||||
public bool Setting_CopyBlendShapes = true;
|
||||
|
||||
#endregion Settings
|
||||
|
||||
#region Source Collections - Cloned Renderers
|
||||
|
||||
// Skinned mesh renderers (always cloned)
|
||||
private List<SkinnedMeshRenderer> _skinnedRenderers;
|
||||
private List<List<float>> _blendShapeWeights;
|
||||
|
||||
// Mesh renderers (optionally cloned)
|
||||
private List<MeshRenderer> _meshRenderers;
|
||||
private List<MeshFilter> _meshFilters;
|
||||
|
||||
#endregion Source Collections - Cloned Renderers
|
||||
|
||||
#region Source Collections - Non-Cloned Renderers
|
||||
|
||||
// All other renderers (never cloned)
|
||||
private List<Renderer> _otherRenderers;
|
||||
|
||||
// True if source renderer should hide. False if source renderer should show.
|
||||
// Only used for non-cloned renderers (MeshRenderers and other Renderers).
|
||||
private bool[] _sourceShouldBeHiddenFromFPR;
|
||||
// Three states: On, ShadowsOnly, Off
|
||||
private ShadowCastingMode[] _originalShadowCastingMode;
|
||||
|
||||
#endregion Source Collections - Non-Cloned Renderers
|
||||
|
||||
#region Clone Collections
|
||||
|
||||
// Skinned mesh clones
|
||||
private List<SkinnedMeshRenderer> _skinnedClones;
|
||||
private List<Material[]> _skinnedCloneMaterials;
|
||||
private List<Material[]> _skinnedCloneCullingMaterials;
|
||||
|
||||
// Mesh clones (optional)
|
||||
private List<MeshRenderer> _meshClones;
|
||||
private List<MeshFilter> _meshCloneFilters;
|
||||
private List<Material[]> _meshCloneMaterials;
|
||||
private List<Material[]> _meshCloneCullingMaterials;
|
||||
|
||||
#endregion Clone Collections
|
||||
|
||||
#region Shared Resources
|
||||
|
||||
private List<Material> _materialWorkingList; // Used for GetSharedMaterials
|
||||
private MaterialPropertyBlock _propertyBlock;
|
||||
|
||||
#endregion Shared Resources
|
||||
|
||||
#region State
|
||||
|
||||
private bool _sourcesSetForShadowCasting;
|
||||
private bool _clonesSetForUiCulling;
|
||||
private bool[] _rendererActiveStates;
|
||||
|
||||
#endregion State
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Setting_CloneMeshRenderers = AvatarCloneTestMod.EntryCloneMeshRenderers.Value;
|
||||
|
||||
InitializeCollections();
|
||||
CollectRenderers();
|
||||
CreateClones();
|
||||
AddExclusionToHeadIfNeeded();
|
||||
InitializeExclusions();
|
||||
SetupMagicaClothSupport();
|
||||
|
||||
// bool animatesClone = transform.Find("[ExplicitlyAnimatesVisualClones]") != null;
|
||||
// Setting_CopyMaterials = !animatesClone;
|
||||
// Setting_CopyBlendShapes = !animatesClone;
|
||||
// Animator animator = GetComponent<Animator>();
|
||||
// if (animator && animatesClone) animator.Rebind();
|
||||
|
||||
// Likely a Unity bug with where we can touch shadowCastingMode & forceRenderingOff
|
||||
#if !UNITY_EDITOR
|
||||
Camera.onPreCull += MyOnPreCull;
|
||||
#else
|
||||
Camera.onPreRender += MyOnPreCull;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
SyncEnabledState();
|
||||
|
||||
if (Setting_CopyMaterials && AvatarCloneTestMod.EntryCopyMaterials.Value)
|
||||
SyncMaterials();
|
||||
|
||||
if (Setting_CopyBlendShapes && AvatarCloneTestMod.EntryCopyBlendShapes.Value)
|
||||
SyncBlendShapes();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Likely a Unity bug with where we can touch shadowCastingMode & forceRenderingOff
|
||||
#if !UNITY_EDITOR
|
||||
Camera.onPreCull -= MyOnPreCull;
|
||||
#else
|
||||
Camera.onPreRender -= MyOnPreCull;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.TransformHider;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.Camera;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarCloneTest;
|
||||
|
||||
public static class Patches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))]
|
||||
private static bool OnSetupAvatar(GameObject avatar)
|
||||
{
|
||||
if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) return true;
|
||||
avatar.AddComponent<AvatarClone>();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
using System.Reflection;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
#region Melon Preference Helpers
|
||||
|
||||
private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry<bool> entry)
|
||||
{
|
||||
ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value);
|
||||
toggle.OnValueUpdated += b => entry.Value = b;
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry<float> entry, float min,
|
||||
float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
{
|
||||
SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description,
|
||||
Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
slider.OnValueUpdated += f => entry.Value = f;
|
||||
return slider;
|
||||
}
|
||||
|
||||
private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry<string> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s);
|
||||
return button;
|
||||
}
|
||||
|
||||
private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry<float> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f);
|
||||
return button;
|
||||
}
|
||||
|
||||
// private static SliderFloat AddMelonSlider(ref Page page, MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
// {
|
||||
// SliderFloat slider = page.AddSlider(entry.DisplayName, entry.Description, Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
// slider.OnValueUpdated += f => entry.Value = f;
|
||||
// return slider;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a category that saves its collapsed state to a MelonPreferences entry.
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="showHeader"></param>
|
||||
/// <returns></returns>
|
||||
private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry<bool> entry, bool showHeader = true)
|
||||
{
|
||||
Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value);
|
||||
category.OnCollapse += b => entry.Value = b;
|
||||
return category;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Icon Utils
|
||||
|
||||
private static Stream GetIconStream(string iconName)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace Popcron;
|
||||
|
||||
public class Constants
|
||||
{
|
||||
public const string UniqueIdentifier = "Popcron.Gizmos";
|
||||
public const string EnabledKey = UniqueIdentifier + ".Enabled";
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Popcron;
|
||||
|
||||
public class LineDrawer : Drawer
|
||||
{
|
||||
public LineDrawer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override int Draw(ref Vector3[] buffer, params object[] args)
|
||||
{
|
||||
buffer[0] = (Vector3)args[0];
|
||||
buffer[1] = (Vector3)args[1];
|
||||
return 2;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Popcron;
|
||||
|
||||
internal class Element
|
||||
{
|
||||
public Vector3[] points = { };
|
||||
public Color color = Color.white;
|
||||
public bool dashed = false;
|
||||
public Matrix4x4 matrix = Matrix4x4.identity;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>SpawnableReceiveOwnChanges</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,308 +0,0 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using System.Reflection;
|
||||
using cohtml.Net;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace NAK.ControlToUnlockMouse;
|
||||
|
||||
public class ControlToUnlockMouseMod : MelonMod
|
||||
{
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(ControlToUnlockMouseMod));
|
||||
|
||||
internal static readonly MelonPreferences_Entry<NoRotatePivotPoint> EntryOriginPivotPoint =
|
||||
Category.CreateEntry("no_rotate_pivot_point", NoRotatePivotPoint.Pickupable,
|
||||
"NoRotation Pickupable Pivot Point", "The pivot point to use when no rotation object is grabbed.");
|
||||
|
||||
public enum NoRotatePivotPoint
|
||||
{
|
||||
Pickupable,
|
||||
AvatarHead,
|
||||
AvatarChest,
|
||||
AvatarClosestShoulder,
|
||||
}
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Awake),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnPlayerSetupAwake),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVR_MenuManager).GetMethod(nameof(CVR_MenuManager.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnMenuManagerStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandleUnityUI),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnControllerRayHandleUnityUIDirectAndIndirect),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandleIndirectUnityUI),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnControllerRayHandleUnityUIDirectAndIndirect),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(ControllerRay).GetMethod(nameof(ControllerRay.LateUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnPreControllerRayLateUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Static)),
|
||||
postfix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnPostControllerRayLateUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
}
|
||||
|
||||
private static void OnPlayerSetupAwake(PlayerSetup __instance)
|
||||
{
|
||||
// Get original fields
|
||||
LayerMask layerMask = __instance.desktopRay.generalMask;
|
||||
|
||||
// Destroy the existing desktop ray
|
||||
Object.Destroy(__instance.desktopRay);
|
||||
|
||||
// Get the desktop camera
|
||||
Camera desktopCam = __instance.desktopCam;
|
||||
|
||||
// Create a new child object under the desktop camera for the ray
|
||||
GameObject rayObject = new("DesktopRay")
|
||||
{
|
||||
transform =
|
||||
{
|
||||
parent = desktopCam.transform,
|
||||
localPosition = Vector3.zero,
|
||||
localRotation = Quaternion.identity
|
||||
}
|
||||
};
|
||||
|
||||
// Add ControllerRay component
|
||||
ControllerRay newRay = rayObject.AddComponent<ControllerRay>();
|
||||
newRay.isDesktopRay = true;
|
||||
newRay.isInteractionRay = true;
|
||||
newRay.RayDirection = Vector3.forward;
|
||||
newRay.generalMask = layerMask;
|
||||
newRay.hand = CVRHand.Right; // Important to even work
|
||||
newRay.attachmentDistance = 0f;
|
||||
newRay.currentAttachmentDistance = 0f;
|
||||
|
||||
// Assign new ray to desktopRay field
|
||||
__instance.desktopRay = newRay;
|
||||
|
||||
// Add our custom controller script
|
||||
DesktopRayController rayController = rayObject.AddComponent<DesktopRayController>();
|
||||
rayController.controllerRay = newRay;
|
||||
rayController.desktopCamera = desktopCam;
|
||||
}
|
||||
|
||||
private static void OnMenuManagerStart(CVR_MenuManager __instance)
|
||||
{
|
||||
__instance.desktopControllerRay = PlayerSetup.Instance.desktopRay;
|
||||
}
|
||||
|
||||
private static bool OnControllerRayHandleUnityUIDirectAndIndirect(ControllerRay __instance)
|
||||
{
|
||||
return !__instance.isDesktopRay || Cursor.lockState == CursorLockMode.Locked;
|
||||
}
|
||||
|
||||
private static void OnPreControllerRayLateUpdate(ControllerRay __instance, ref bool __state)
|
||||
{
|
||||
if (!__instance.isDesktopRay)
|
||||
return;
|
||||
|
||||
ViewManager menu = ViewManager.Instance;
|
||||
__state = menu._gameMenuOpen;
|
||||
|
||||
if (!__state) menu._gameMenuOpen = Cursor.lockState != CursorLockMode.Locked;
|
||||
}
|
||||
|
||||
private static void OnPostControllerRayLateUpdate(ControllerRay __instance, ref bool __state)
|
||||
{
|
||||
if (!__instance.isDesktopRay) return;
|
||||
ViewManager.Instance._gameMenuOpen = __state;
|
||||
}
|
||||
}
|
||||
|
||||
public class DesktopRayController : MonoBehaviour
|
||||
{
|
||||
internal ControllerRay controllerRay;
|
||||
internal Camera desktopCamera;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Toggle desktop mouse mode based on Control key state
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl))
|
||||
{
|
||||
if (!ViewManager.Instance.IsAnyMenuOpen) RootLogic.CursorLock(false);
|
||||
}
|
||||
|
||||
if (Input.GetKeyUp(KeyCode.LeftControl))
|
||||
{
|
||||
if (!ViewManager.Instance.IsAnyMenuOpen) RootLogic.CursorLock(true);
|
||||
}
|
||||
|
||||
Transform rayRoot = controllerRay.transform;
|
||||
Transform rayDirection = controllerRay.rayDirectionTransform;
|
||||
Transform attachment = controllerRay.attachmentPoint;
|
||||
Camera cam = desktopCamera;
|
||||
|
||||
if (Cursor.lockState == CursorLockMode.Locked)
|
||||
{
|
||||
// Reset local position when unlocked
|
||||
rayRoot.localPosition = Vector3.zero;
|
||||
rayRoot.localRotation = Quaternion.identity;
|
||||
|
||||
// Reset local position and rotation when locked
|
||||
rayDirection.localPosition = new Vector3(0f, 0f, 0.001f);
|
||||
rayDirection.localRotation = Quaternion.identity;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isAnyMenuOpen = ViewManager.Instance.IsAnyMenuOpen;
|
||||
Pickupable grabbedObject = controllerRay.grabbedObject;
|
||||
|
||||
// Only do when not holding an origin object
|
||||
Vector3 screenPos = new(Input.mousePosition.x, Input.mousePosition.y);
|
||||
|
||||
if (isAnyMenuOpen)
|
||||
{
|
||||
// Center the ray
|
||||
rayRoot.localPosition = Vector3.zero;
|
||||
}
|
||||
else if (grabbedObject && !grabbedObject.IsObjectRotationAllowed)
|
||||
{
|
||||
// Specialized movement of ray around pickupable pivot
|
||||
Vector3 pivotPoint = grabbedObject.transform.position;
|
||||
Vector3 pivotPointCenter = grabbedObject.RootTransform.position;
|
||||
|
||||
PlayerSetup playerSetup = PlayerSetup.Instance;
|
||||
if (playerSetup != null && playerSetup._animator != null && playerSetup._animator.isHuman)
|
||||
{
|
||||
Animator animator = playerSetup._animator;
|
||||
switch (ControlToUnlockMouseMod.EntryOriginPivotPoint.Value)
|
||||
{
|
||||
case ControlToUnlockMouseMod.NoRotatePivotPoint.AvatarHead:
|
||||
{
|
||||
Transform headBone = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
if (headBone != null) pivotPoint = headBone.position;
|
||||
break;
|
||||
}
|
||||
case ControlToUnlockMouseMod.NoRotatePivotPoint.AvatarChest:
|
||||
{
|
||||
if (playerSetup._avatar != null)
|
||||
{
|
||||
Transform chestBone = animator.GetBoneTransform(HumanBodyBones.Chest);
|
||||
if (chestBone != null) pivotPoint = chestBone.position;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ControlToUnlockMouseMod.NoRotatePivotPoint.AvatarClosestShoulder:
|
||||
{
|
||||
if (playerSetup._avatar != null)
|
||||
{
|
||||
Transform leftShoulder = animator.GetBoneTransform(HumanBodyBones.LeftShoulder);
|
||||
Transform rightShoulder = animator.GetBoneTransform(HumanBodyBones.RightShoulder);
|
||||
if (leftShoulder != null || rightShoulder != null)
|
||||
{
|
||||
if (leftShoulder != null && rightShoulder != null)
|
||||
{
|
||||
pivotPoint = Vector3.Distance(leftShoulder.position, pivotPoint) < Vector3.Distance(rightShoulder.position, pivotPoint)
|
||||
? leftShoulder.position
|
||||
: rightShoulder.position;
|
||||
}
|
||||
else if (leftShoulder != null)
|
||||
{
|
||||
pivotPoint = leftShoulder.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
pivotPoint = rightShoulder.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ControlToUnlockMouseMod.NoRotatePivotPoint.Pickupable:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get local position of pivotPoint relative to rayRoot
|
||||
// This is shit but i cant wrap my head around the proper way to compute this lol
|
||||
Vector3 localPivotPoint = rayRoot.InverseTransformPoint(pivotPoint);
|
||||
Vector3 localPivotPointCenter = rayRoot.InverseTransformPoint(pivotPointCenter);
|
||||
localPivotPoint.x = localPivotPointCenter.x; // Maintain local X
|
||||
localPivotPoint.y = localPivotPointCenter.y; // Maintain local Y
|
||||
|
||||
// Compute target world position based on the mouse and attachment distance.
|
||||
screenPos.z = 10f;
|
||||
Vector3 targetWorldPos = cam.ScreenToWorldPoint(screenPos);
|
||||
|
||||
// Desired direction from the pivot point (grabbed object) to the target world position.
|
||||
Vector3 directionToTarget = targetWorldPos - rayRoot.TransformPoint(localPivotPoint);;
|
||||
|
||||
if (directionToTarget.sqrMagnitude < 1e-6f)
|
||||
directionToTarget = rayRoot.forward; // Fallback if mouse is centered
|
||||
|
||||
// Calculate the target rotation for rayRoot.
|
||||
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget, cam.transform.up);
|
||||
|
||||
// Get the current local offset of the grabbed object relative to rayRoot.
|
||||
Vector3 localPickupOffset = rayRoot.InverseTransformPoint(pivotPoint);
|
||||
|
||||
// Compute the new rayRoot position to keep the grabbed object (child) at pivotPoint.
|
||||
Vector3 newRayRootPos = pivotPoint - (targetRotation * localPickupOffset);
|
||||
|
||||
// Apply the new rotation and position.
|
||||
rayRoot.rotation = targetRotation;
|
||||
rayRoot.position = newRayRootPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
float distance;
|
||||
if (grabbedObject)
|
||||
{
|
||||
// This position is calculated basically same way as below in BasePickupHandler,
|
||||
// but not determined by ray hit
|
||||
distance = attachment.localPosition.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute distance forward from ray
|
||||
Vector3 localOffset = rayRoot.InverseTransformPoint(controllerRay._hit.point);
|
||||
distance = localOffset.z;
|
||||
}
|
||||
|
||||
screenPos.z = distance;
|
||||
|
||||
// Compute world position from where mouse is on screen
|
||||
Vector3 worldPos = cam.ScreenToWorldPoint(screenPos);
|
||||
|
||||
// Normal movement of ray
|
||||
Vector3 newLocalPos = rayRoot.parent.InverseTransformPoint(worldPos);
|
||||
newLocalPos.z = rayRoot.localPosition.z; // Maintain local Z
|
||||
rayRoot.localPosition = newLocalPos;
|
||||
}
|
||||
|
||||
// Compute mouse ray in world space
|
||||
Ray mouseRay = cam.ScreenPointToRay(Input.mousePosition);
|
||||
rayDirection.position = mouseRay.origin;
|
||||
rayDirection.rotation = Quaternion.LookRotation(mouseRay.direction, cam.transform.up);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using MelonLoader;
|
||||
using NAK.ControlToUnlockMouse.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.ControlToUnlockMouse))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.ControlToUnlockMouse))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.ControlToUnlockMouse.ControlToUnlockMouseMod),
|
||||
nameof(NAK.ControlToUnlockMouse),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ControlToUnlockMouse"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "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.ControlToUnlockMouse.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>LoadedObjectHack</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,49 +0,0 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Player;
|
||||
using HarmonyLib;
|
||||
using MagicaCloth2;
|
||||
using MelonLoader;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
|
||||
namespace NAK.FuckOffMagicaCloth2;
|
||||
|
||||
public class FuckOffMagicaCloth2Mod : MelonMod
|
||||
{
|
||||
private static MelonLogger.Instance Logger;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
ApplyPatches(typeof(MagicaCloth_Patches));
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class MagicaCloth_Patches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(MagicaCloth2.MagicaCloth), nameof(MagicaCloth2.MagicaCloth.Awake))]
|
||||
private static void MagicaCloth_Awake_Prefix(MagicaCloth2.MagicaCloth __instance)
|
||||
{
|
||||
__instance.SerializeData.selfCollisionConstraint.selfMode = SelfCollisionConstraint.SelfCollisionMode.None;
|
||||
__instance.SerializeData.selfCollisionConstraint.syncMode = SelfCollisionConstraint.SelfCollisionMode.None;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using MelonLoader;
|
||||
using NAK.FuckOffMagicaCloth2.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.FuckOffMagicaCloth2))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.FuckOffMagicaCloth2))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.FuckOffMagicaCloth2.FuckOffMagicaCloth2Mod),
|
||||
nameof(NAK.FuckOffMagicaCloth2),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckOffMagicaCloth2"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.FuckOffMagicaCloth2.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.1";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "AASDefaultProfileFix",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2024r175",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Fixes the Default AAS profile not being applied when loading into an avatar without a profile selected.\n\nBy default, the game will not apply anything and the avatar will default to the state found within the Controller parameters.",
|
||||
"searchtags": [
|
||||
"aas",
|
||||
"profile",
|
||||
"default",
|
||||
"fix",
|
||||
"meow"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r33/AASDefaultProfileFix.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AASDefaultProfileFix/",
|
||||
"changelog": "- Initial release",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
using ABI_RC.Core.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.FuckOffUICamera;
|
||||
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public class CohtmlRenderForwarder : MonoBehaviour
|
||||
{
|
||||
#region Private Variables
|
||||
|
||||
private CohtmlControlledView[] controlledViews;
|
||||
|
||||
#endregion Private Variables
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void OnPreRender()
|
||||
{
|
||||
if (controlledViews == null) return;
|
||||
foreach (CohtmlControlledView view in controlledViews)
|
||||
if (view) view.OnPreRender();
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static void Setup(Camera camera, params CohtmlControlledView[] views)
|
||||
{
|
||||
CohtmlRenderForwarder forwarder = camera.gameObject.AddComponent<CohtmlRenderForwarder>();
|
||||
forwarder.controlledViews = views;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NAK.FuckOffUICamera;
|
||||
|
||||
public class CommandBufferManager : MonoBehaviour
|
||||
{
|
||||
#region Private Variables
|
||||
|
||||
private CommandBuffer commandBuffer;
|
||||
private Camera targetCamera;
|
||||
private Renderer[] targetRenderers;
|
||||
private bool[] rendererEnabledStates;
|
||||
private const string CommandBufferName = "CustomRenderPass";
|
||||
private bool _didSetup;
|
||||
|
||||
#endregion Private Variables
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private IEnumerator Start()
|
||||
{
|
||||
yield return new WaitForSeconds(2f); // I have no idea why this needs to be delayed
|
||||
_didSetup = true;
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!_didSetup) return;
|
||||
if (targetCamera == null || targetRenderers == null)
|
||||
return;
|
||||
|
||||
SetupEnabledStateCollection();
|
||||
SetupCommandBuffer();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
CleanupCommandBuffer();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (targetRenderers == null
|
||||
|| rendererEnabledStates == null)
|
||||
return;
|
||||
|
||||
bool needsRebuild = false;
|
||||
|
||||
// Check if any renderer enabled states have changed
|
||||
int targetRenderersLength = targetRenderers.Length;
|
||||
for (int i = 0; i < targetRenderersLength; i++)
|
||||
{
|
||||
if (targetRenderers[i] == null) continue;
|
||||
|
||||
bool currentState = targetRenderers[i].enabled && targetRenderers[i].gameObject.activeInHierarchy;
|
||||
if (currentState == rendererEnabledStates[i])
|
||||
continue;
|
||||
|
||||
rendererEnabledStates[i] = currentState;
|
||||
needsRebuild = true;
|
||||
}
|
||||
|
||||
if (needsRebuild) RebuildCommandBuffer();
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static void Setup(Camera camera, params Renderer[] renderers)
|
||||
{
|
||||
CommandBufferManager manager = camera.gameObject.AddComponent<CommandBufferManager>();
|
||||
manager.targetCamera = camera;
|
||||
manager.targetRenderers = renderers;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void SetupEnabledStateCollection()
|
||||
{
|
||||
if (rendererEnabledStates != null)
|
||||
Array.Resize(ref rendererEnabledStates, targetRenderers.Length);
|
||||
else
|
||||
rendererEnabledStates = new bool[targetRenderers.Length];
|
||||
}
|
||||
|
||||
private void SetupCommandBuffer()
|
||||
{
|
||||
commandBuffer = new CommandBuffer();
|
||||
commandBuffer.name = CommandBufferName;
|
||||
|
||||
// Set render target and clear depth
|
||||
commandBuffer.SetRenderTarget(new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget,
|
||||
0, CubemapFace.Unknown, RenderTargetIdentifier.AllDepthSlices));
|
||||
|
||||
commandBuffer.ClearRenderTarget(true, false, Color.clear);
|
||||
|
||||
for (int i = 0; i < targetRenderers.Length; i++)
|
||||
{
|
||||
Renderer renderer = targetRenderers[i];
|
||||
if (renderer == null || !rendererEnabledStates[i])
|
||||
continue;
|
||||
|
||||
commandBuffer.DrawRenderer(renderer, renderer.sharedMaterial);
|
||||
renderer.forceRenderingOff = true;
|
||||
}
|
||||
|
||||
targetCamera.AddCommandBuffer(CameraEvent.AfterImageEffects, commandBuffer);
|
||||
|
||||
Debug.Log($"Command buffer setup for {targetCamera.name} with {targetRenderers.Length} renderers.");
|
||||
}
|
||||
|
||||
private void RebuildCommandBuffer()
|
||||
{
|
||||
CleanupCommandBuffer();
|
||||
SetupCommandBuffer();
|
||||
}
|
||||
|
||||
private void CleanupCommandBuffer()
|
||||
{
|
||||
if (targetCamera == null || commandBuffer == null)
|
||||
return;
|
||||
|
||||
// Re-enable normal rendering for all renderers
|
||||
if (targetRenderers != null)
|
||||
{
|
||||
foreach (Renderer renderer in targetRenderers)
|
||||
{
|
||||
if (renderer != null)
|
||||
renderer.forceRenderingOff = false;
|
||||
}
|
||||
}
|
||||
|
||||
targetCamera.RemoveCommandBuffer(CameraEvent.AfterImageEffects, commandBuffer);
|
||||
commandBuffer = null;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk" />
|
|
@ -1,73 +0,0 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.UI;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace NAK.FuckOffUICamera;
|
||||
|
||||
public class FuckOffUICameraMod : MelonMod
|
||||
{
|
||||
private static MelonLogger.Instance Logger;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
}
|
||||
|
||||
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
|
||||
{
|
||||
if (buildIndex != 2) return;
|
||||
if (_isInitialized) return;
|
||||
SetupShittyMod();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
private static void SetupShittyMod()
|
||||
{
|
||||
// Find all renderers under Cohtml object
|
||||
GameObject cohtml = GameObject.Find("Cohtml");
|
||||
if (cohtml == null)
|
||||
{
|
||||
Logger.Error("Cohtml object not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all CohtmlControlledView objects
|
||||
var allMenuCohtml = Object.FindObjectsOfType<CohtmlControlledView>(includeInactive: true);
|
||||
var allUiInternalRenderers = Object.FindObjectsOfType<Renderer>(includeInactive: true)
|
||||
.Where(x => x.gameObject.layer == CVRLayers.UIInternal)
|
||||
.ToArray();
|
||||
|
||||
//var allMenuRenderers = cohtml.GetComponentsInChildren<Renderer>(true);
|
||||
|
||||
// Add hud renderer to the list of renderers
|
||||
Renderer hudRenderer = CohtmlHud.Instance.GetComponent<Renderer>();
|
||||
// Array.Resize(ref allMenuRenderers, allMenuRenderers.Length + 1);
|
||||
// allMenuRenderers[^1] = hudRenderer;
|
||||
|
||||
// Fix shader on the hud renderer
|
||||
Material material = hudRenderer.sharedMaterial;
|
||||
material.shader = Shader.Find("Alpha Blend Interactive/MenuFX");
|
||||
|
||||
// Setup command buffer manager for desktop camera
|
||||
CommandBufferManager.Setup(PlayerSetup.Instance.desktopCam, allUiInternalRenderers);
|
||||
CohtmlRenderForwarder.Setup(PlayerSetup.Instance.desktopCam, allMenuCohtml);
|
||||
|
||||
// Setup command buffer manager for vr camera
|
||||
CommandBufferManager.Setup(PlayerSetup.Instance.vrCam, allUiInternalRenderers);
|
||||
CohtmlRenderForwarder.Setup(PlayerSetup.Instance.vrCam, allMenuCohtml);
|
||||
|
||||
// Disable the ui cameras
|
||||
PlayerSetup.Instance.desktopUiCam.gameObject.SetActive(false);
|
||||
PlayerSetup.Instance.vrUiCam.gameObject.SetActive(false);
|
||||
|
||||
Logger.Msg("Disabled UI cameras and setup command buffer manager for Cohtml renderers.");
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using MelonLoader;
|
||||
using NAK.FuckOffUICamera.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.FuckOffUICamera))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.FuckOffUICamera))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.FuckOffUICamera.FuckOffUICameraMod),
|
||||
nameof(NAK.FuckOffUICamera),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckOffUICamera"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "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.FuckOffUICamera.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
# SearchWithSpacesFix
|
||||
|
||||
Fixes search terms that use spaces.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation 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.
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "SearchWithSpacesFix",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2024r177",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Fixes search terms that include spaces.",
|
||||
"searchtags": [
|
||||
"search",
|
||||
"spaces",
|
||||
"fix",
|
||||
"meow"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r42/SearchWithSpacesFix.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix/",
|
||||
"changelog": "- Initial release",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
# IKSimulatedRootAngleFix
|
||||
|
||||
Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation 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.
|
|
@ -1,100 +0,0 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.LegacyContentMitigation;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace LegacyContentMitigation.Components;
|
||||
|
||||
public class FaceMirror : MonoBehaviour
|
||||
{
|
||||
private Camera _parentCamera;
|
||||
private Camera _camera;
|
||||
public Rect shiftRect;
|
||||
private CommandBuffer _viewportBuffer;
|
||||
|
||||
private void Start() {
|
||||
_parentCamera = GetComponent<Camera>();
|
||||
_camera = new GameObject("Face Mirror").AddComponent<Camera>();
|
||||
_camera.transform.parent = transform;
|
||||
_camera.CopyFrom(_parentCamera);
|
||||
_camera.ResetReplacementShader();
|
||||
_camera.depth = 99;
|
||||
_camera.clearFlags = CameraClearFlags.Depth;
|
||||
_camera.transform.position += transform.forward * 0.5f;
|
||||
_camera.transform.rotation *= Quaternion.Euler(0, 180, 0);
|
||||
|
||||
// View only CVRLayers.PlayerLocal
|
||||
_camera.cullingMask = 1 << CVRLayers.PlayerLocal;
|
||||
|
||||
// Create and cache the command buffer
|
||||
_viewportBuffer = new CommandBuffer();
|
||||
_viewportBuffer.SetViewport(shiftRect);
|
||||
|
||||
_camera.AddCommandBuffer(CameraEvent.BeforeDepthTexture, _viewportBuffer);
|
||||
_camera.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, _viewportBuffer);
|
||||
_camera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _viewportBuffer);
|
||||
_camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, _viewportBuffer);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (ModSettings.EntryUseFaceMirror.Value == false)
|
||||
{
|
||||
_camera.enabled = false;
|
||||
return;
|
||||
}
|
||||
_camera.enabled = true;
|
||||
|
||||
// Update camera distance
|
||||
_camera.transform.localPosition = Vector3.forward * ModSettings.EntryFaceMirrorDistance.Value;
|
||||
|
||||
// Get the display resolution based on VR status
|
||||
int displayWidth, displayHeight;
|
||||
if (MetaPort.Instance.isUsingVr)
|
||||
{
|
||||
displayWidth = XRSettings.eyeTextureWidth;
|
||||
displayHeight = XRSettings.eyeTextureHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayWidth = Screen.width;
|
||||
displayHeight = Screen.height;
|
||||
}
|
||||
|
||||
// Calculate pixel sizes first
|
||||
float pixelSizeX = ModSettings.EntryFaceMirrorSizeX.Value * displayWidth;
|
||||
float pixelSizeY = ModSettings.EntryFaceMirrorSizeY.Value * displayHeight;
|
||||
|
||||
// Calculate offsets from center
|
||||
float pixelOffsetX = (ModSettings.EntryFaceMirrorOffsetX.Value * displayWidth) - (pixelSizeX * 0.5f) + (displayWidth * 0.5f);
|
||||
float pixelOffsetY = (ModSettings.EntryFaceMirrorOffsetY.Value * displayHeight) - (pixelSizeY * 0.5f) + (displayHeight * 0.5f);
|
||||
|
||||
_camera.transform.localScale = Vector3.one * ModSettings.EntryFaceMirrorCameraScale.Value;
|
||||
|
||||
Vector3 playerup = PlayerSetup.Instance.transform.up;
|
||||
Vector3 cameraForward = _parentCamera.transform.forward;
|
||||
|
||||
// Check if playerup and cameraForward are nearly aligned
|
||||
if (Mathf.Abs(Vector3.Dot(playerup, cameraForward)) <= Mathf.Epsilon) {
|
||||
playerup = -_parentCamera.transform.forward;
|
||||
cameraForward = _parentCamera.transform.up;
|
||||
}
|
||||
|
||||
_camera.transform.rotation = Quaternion.LookRotation(-cameraForward, playerup);
|
||||
|
||||
// Create viewport rect with pixel values
|
||||
shiftRect = new Rect(
|
||||
pixelOffsetX,
|
||||
pixelOffsetY,
|
||||
pixelSizeX,
|
||||
pixelSizeY
|
||||
);
|
||||
|
||||
// Update the cached buffer's viewport
|
||||
_viewportBuffer.Clear();
|
||||
_viewportBuffer.SetViewport(shiftRect);
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
using MelonLoader;
|
||||
|
||||
namespace NAK.LegacyContentMitigation;
|
||||
|
||||
internal static class ModSettings
|
||||
{
|
||||
#region Constants
|
||||
|
||||
internal const string ModName = nameof(LegacyContentMitigation);
|
||||
internal const string LCM_SettingsCategory = "Legacy Content Mitigation";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Melon Preferences
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(ModName);
|
||||
|
||||
internal static readonly MelonPreferences_Entry<bool> EntryAutoForLegacyWorlds =
|
||||
Category.CreateEntry("auto_for_legacy_worlds", true,
|
||||
"Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<float> EntryFaceMirrorDistance =
|
||||
Category.CreateEntry("face_mirror_distance", 0.5f,
|
||||
"Face Mirror Distance", description: "Distance from the camera to place the face mirror.");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<float> EntryFaceMirrorOffsetX =
|
||||
Category.CreateEntry("face_mirror_offset_x", 0f,
|
||||
"Face Mirror Offset X", description: "Offset the face mirror on the X axis.");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<float> EntryFaceMirrorOffsetY =
|
||||
Category.CreateEntry("face_mirror_offset_y", 0f,
|
||||
"Face Mirror Offset Y", description: "Offset the face mirror on the Y axis.");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<float> EntryFaceMirrorSizeX =
|
||||
Category.CreateEntry("face_mirror_size_x", 0.5f,
|
||||
"Face Mirror Size X", description: "Size of the face mirror on the X axis.");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<float> EntryFaceMirrorSizeY =
|
||||
Category.CreateEntry("face_mirror_size_y", 0.5f,
|
||||
"Face Mirror Size Y", description: "Size of the face mirror on the Y axis.");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<float> EntryFaceMirrorCameraScale =
|
||||
Category.CreateEntry("face_mirror_camera_scale", 1f,
|
||||
"Face Mirror Camera Scale", description: "Scale of the face mirror camera.");
|
||||
|
||||
internal static readonly MelonPreferences_Entry<bool> EntryUseFaceMirror =
|
||||
Category.CreateEntry("use_face_mirror", true,
|
||||
"Use Face Mirror", description: "Should the face mirror be used?");
|
||||
|
||||
#endregion Melon Preferences
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core.Player;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RemoteAvatarDisablingCameraOnFirstFrameFix;
|
||||
|
||||
public class RemoteAvatarDisablingCameraOnFirstFrameFixMod : MelonMod
|
||||
{
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.AvatarInstantiated),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(RemoteAvatarDisablingCameraOnFirstFrameFixMod).GetMethod(nameof(OnPuppetMasterAvatarInstantiated),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
}
|
||||
|
||||
private static void OnPuppetMasterAvatarInstantiated(PuppetMaster __instance)
|
||||
{
|
||||
if (__instance._animator == null) return;
|
||||
|
||||
__instance._animator.WriteDefaultValues();
|
||||
__instance._animator.keepAnimatorStateOnDisable = false;
|
||||
__instance._animator.writeDefaultValuesOnDisable = false;
|
||||
}
|
||||
|
||||
// private static void OnPuppetMasterAvatarInstantiated(PuppetMaster __instance)
|
||||
// {
|
||||
// if (__instance._animator == null) return;
|
||||
//
|
||||
// // Set culling mode to always animate
|
||||
// __instance._animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||
//
|
||||
// // Update the animator to force it to do the first frame
|
||||
// __instance._animator.Update(0f);
|
||||
//
|
||||
// // Set culling mode back to cull update transforms
|
||||
// __instance._animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
|
||||
// }
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using MelonLoader;
|
||||
using NAK.RemoteAvatarDisablingCameraOnFirstFrameFix.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix.RemoteAvatarDisablingCameraOnFirstFrameFixMod),
|
||||
nameof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RemoteAvatarDisablingCameraOnFirstFrameFix"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.RemoteAvatarDisablingCameraOnFirstFrameFix.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
# IKSimulatedRootAngleFix
|
||||
|
||||
Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation 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.
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>LoadedObjectHack</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "AASDefaultProfileFix",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2024r175",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Fixes the Default AAS profile not being applied when loading into an avatar without a profile selected.\n\nBy default, the game will not apply anything and the avatar will default to the state found within the Controller parameters.",
|
||||
"searchtags": [
|
||||
"aas",
|
||||
"profile",
|
||||
"default",
|
||||
"fix",
|
||||
"meow"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r33/AASDefaultProfileFix.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AASDefaultProfileFix/",
|
||||
"changelog": "- Initial release",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
# SearchWithSpacesFix
|
||||
|
||||
Fixes search terms that use spaces.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation 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.
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "SearchWithSpacesFix",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2024r177",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Fixes search terms that include spaces.",
|
||||
"searchtags": [
|
||||
"search",
|
||||
"spaces",
|
||||
"fix",
|
||||
"meow"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r42/SearchWithSpacesFix.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix/",
|
||||
"changelog": "- Initial release",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction
|
||||
{
|
||||
public class CVRPlayerHand : MonoBehaviour
|
||||
{
|
||||
#region Fields
|
||||
|
||||
[SerializeField]
|
||||
private CVRHand _hand;
|
||||
|
||||
// Pickup rig
|
||||
[SerializeField] private Transform rayDirection;
|
||||
[SerializeField] private Transform _attachmentPoint;
|
||||
[SerializeField] private Transform _pivotPoint;
|
||||
[SerializeField] private VelocityTracker _velocityTracker;
|
||||
|
||||
// Pickup state
|
||||
private bool _isHoldingObject;
|
||||
private Pickupable _heldPickupable;
|
||||
private Pickupable _proximityPickupable;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Unity Events
|
||||
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Private Methods
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Public Methods
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
using ABI_RC.Core.Player.Interaction.RaycastImpl;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction
|
||||
{
|
||||
public class CVRPlayerInteractionManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
public static CVRPlayerInteractionManager Instance { get; private set; }
|
||||
|
||||
#endregion Singleton
|
||||
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Hand Components")]
|
||||
[SerializeField] private CVRPlayerHand handVrLeft;
|
||||
[SerializeField] private CVRPlayerHand handVrRight;
|
||||
[SerializeField] private CVRPlayerHand handDesktopRight; // Desktop does not have a left hand
|
||||
|
||||
[Header("Raycast Transforms")]
|
||||
[SerializeField] private Transform raycastTransformVrRight;
|
||||
[SerializeField] private Transform raycastTransformVrLeft;
|
||||
[SerializeField] private Transform raycastTransformDesktopRight;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private bool interactionEnabled = true;
|
||||
[SerializeField] private LayerMask interactionLayerMask = -1; // Default to all layers, will be filtered
|
||||
|
||||
#endregion Serialized Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
private CVRPlayerHand _rightHand;
|
||||
private CVRPlayerHand _leftHand;
|
||||
|
||||
private CVRPlayerRaycaster _rightRaycaster;
|
||||
private CVRPlayerRaycaster _leftRaycaster;
|
||||
|
||||
private CVRRaycastResult _rightRaycastResult;
|
||||
private CVRRaycastResult _leftRaycastResult;
|
||||
|
||||
// Input handler
|
||||
private CVRPlayerInputHandler _inputHandler;
|
||||
|
||||
// Interaction flags
|
||||
public bool InteractionEnabled
|
||||
{
|
||||
get => interactionEnabled;
|
||||
set => interactionEnabled = value;
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
|
||||
// Create the input handler
|
||||
_inputHandler = gameObject.AddComponent<CVRPlayerInputHandler>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Setup interaction for current device mode
|
||||
SetupInteractionForDeviceMode();
|
||||
|
||||
// Listen for VR mode changes
|
||||
MetaPort.Instance.onVRModeSwitch.AddListener(SetupInteractionForDeviceMode);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!interactionEnabled)
|
||||
return;
|
||||
|
||||
// Process right hand
|
||||
if (_rightRaycaster != null)
|
||||
{
|
||||
// Determine raycast flags based on current mode
|
||||
CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_rightHand);
|
||||
|
||||
// Get raycast results
|
||||
_rightRaycastResult = _rightRaycaster.GetRaycastResults(flags);
|
||||
|
||||
// Process input based on raycast results
|
||||
_inputHandler.ProcessInput(CVRHand.Right, _rightRaycastResult);
|
||||
}
|
||||
|
||||
// Process left hand (if available)
|
||||
if (_leftRaycaster != null)
|
||||
{
|
||||
// Determine raycast flags based on current mode
|
||||
CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_leftHand);
|
||||
|
||||
// Get raycast results
|
||||
_leftRaycastResult = _leftRaycaster.GetRaycastResults(flags);
|
||||
|
||||
// Process input based on raycast results
|
||||
_inputHandler.ProcessInput(CVRHand.Left, _leftRaycastResult);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Clean up event listener
|
||||
if (MetaPort.Instance != null)
|
||||
MetaPort.Instance.onVRModeSwitch.RemoveListener(SetupInteractionForDeviceMode);
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Register a custom tool mode
|
||||
/// </summary>
|
||||
public void RegisterCustomToolMode(System.Action<CVRHand, CVRRaycastResult, InputState> callback)
|
||||
{
|
||||
_inputHandler.RegisterCustomTool(callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister the current custom tool mode
|
||||
/// </summary>
|
||||
public void UnregisterCustomToolMode()
|
||||
{
|
||||
_inputHandler.UnregisterCustomTool();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the interaction mode
|
||||
/// </summary>
|
||||
public void SetInteractionMode(CVRPlayerInputHandler.InteractionMode mode)
|
||||
{
|
||||
_inputHandler.SetInteractionMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the raycast result for a specific hand
|
||||
/// </summary>
|
||||
public CVRRaycastResult GetRaycastResult(CVRHand hand)
|
||||
{
|
||||
return hand == CVRHand.Left ? _leftRaycastResult : _rightRaycastResult;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void SetupInteractionForDeviceMode()
|
||||
{
|
||||
bool isVr = MetaPort.Instance.isUsingVr;
|
||||
|
||||
if (isVr)
|
||||
{
|
||||
// VR mode
|
||||
_rightHand = handVrRight;
|
||||
_leftHand = handVrLeft;
|
||||
|
||||
// VR uses the controller transform for raycasting
|
||||
_rightRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrRight);
|
||||
_leftRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Desktop mode
|
||||
_rightHand = handDesktopRight;
|
||||
_leftHand = null;
|
||||
|
||||
// Desktop uses the mouse position for raycasting when unlocked
|
||||
Camera desktopCamera = PlayerSetup.Instance.desktopCam;
|
||||
_rightRaycaster = new CVRPlayerRaycasterMouse(raycastTransformDesktopRight, desktopCamera);
|
||||
_leftRaycaster = null;
|
||||
}
|
||||
|
||||
// Set the layer mask for raycasters
|
||||
if (_rightRaycaster != null)
|
||||
_rightRaycaster.SetLayerMask(interactionLayerMask);
|
||||
|
||||
if (_leftRaycaster != null)
|
||||
_leftRaycaster.SetLayerMask(interactionLayerMask);
|
||||
}
|
||||
|
||||
private static CVRPlayerRaycaster.RaycastFlags DetermineRaycastFlags(CVRPlayerHand hand)
|
||||
{
|
||||
// Default to all flags
|
||||
CVRPlayerRaycaster.RaycastFlags flags = CVRPlayerRaycaster.RaycastFlags.All;
|
||||
|
||||
// Check if hand is holding a pickup
|
||||
if (hand != null && hand.IsHoldingObject)
|
||||
{
|
||||
// When holding an object, only check for COHTML interaction
|
||||
flags = CVRPlayerRaycaster.RaycastFlags.CohtmlInteract;
|
||||
}
|
||||
|
||||
// Could add more conditional flag adjustments here based on the current mode
|
||||
// For example, in a teleport tool mode, you might only want world hits
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NAK.SuperAwesomeMod.Components
|
||||
{
|
||||
public class CVRCanvasWrapper : MonoBehaviour
|
||||
{
|
||||
public bool IsInteractable = true;
|
||||
public float MaxInteractDistance = 10f;
|
||||
|
||||
private Canvas _canvas;
|
||||
private GraphicRaycaster _graphicsRaycaster;
|
||||
private static readonly List<RaycastResult> _raycastResults = new();
|
||||
private static readonly PointerEventData _pointerEventData = new(EventSystem.current);
|
||||
|
||||
private static Selectable _workingSelectable;
|
||||
private Camera _camera;
|
||||
private RectTransform _rectTransform;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!TryGetComponent(out _canvas)
|
||||
|| _canvas.renderMode != RenderMode.WorldSpace)
|
||||
{
|
||||
IsInteractable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_rectTransform = _canvas.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_graphicsRaycaster = _canvas.gameObject.AddComponent<GraphicRaycaster>();
|
||||
_camera = PlayerSetup.Instance.activeCam;
|
||||
_canvas.worldCamera = _camera;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public bool GetGraphicsHit(Ray worldRay, out RaycastResult result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
if (!IsInteractable || _camera == null) return false;
|
||||
|
||||
// Get the plane of the canvas
|
||||
Plane canvasPlane = new(transform.forward, transform.position);
|
||||
|
||||
// Find where the ray intersects the canvas plane
|
||||
if (!canvasPlane.Raycast(worldRay, out float distance))
|
||||
return false;
|
||||
|
||||
// Get the world point of intersection
|
||||
Vector3 worldHitPoint = worldRay.origin + worldRay.direction * distance;
|
||||
|
||||
// Check if hit point is within max interaction distance
|
||||
if (Vector3.Distance(worldRay.origin, worldHitPoint) > MaxInteractDistance)
|
||||
return false;
|
||||
|
||||
// Check if hit point is within canvas bounds
|
||||
Vector3 localHitPoint = transform.InverseTransformPoint(worldHitPoint);
|
||||
Rect canvasRect = _rectTransform.rect;
|
||||
if (!canvasRect.Contains(new Vector2(localHitPoint.x, localHitPoint.y)))
|
||||
return false;
|
||||
|
||||
// Convert world hit point to screen space
|
||||
Vector2 screenPoint = _camera.WorldToScreenPoint(worldHitPoint);
|
||||
|
||||
// Update pointer event data
|
||||
_pointerEventData.position = screenPoint;
|
||||
_pointerEventData.delta = Vector2.zero;
|
||||
|
||||
// Clear previous results and perform raycast
|
||||
_raycastResults.Clear();
|
||||
_graphicsRaycaster.Raycast(_pointerEventData, _raycastResults);
|
||||
|
||||
// Early out if no hits
|
||||
if (_raycastResults.Count == 0)
|
||||
{
|
||||
//Debug.Log($"No hits on canvas {_canvas.name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find first valid interactive UI element
|
||||
foreach (RaycastResult hit in _raycastResults)
|
||||
{
|
||||
if (!hit.isValid)
|
||||
{
|
||||
//Debug.Log($"Invalid hit on canvas {_canvas.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the hit object has a Selectable component and is interactable
|
||||
GameObject hitObject = hit.gameObject;
|
||||
if (!hitObject.TryGetComponent(out _workingSelectable)
|
||||
|| !_workingSelectable.interactable)
|
||||
{
|
||||
//Debug.Log($"Non-interactable hit on canvas {_canvas.name} - {hitObject.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
//Debug.Log($"Hit on canvas {_canvas.name} with {hitObject.name}");
|
||||
|
||||
result = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
|
@ -1,385 +0,0 @@
|
|||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI.CCK.Components;
|
||||
using NAK.SuperAwesomeMod.Components;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public abstract class CVRPlayerRaycaster
|
||||
{
|
||||
#region Enums
|
||||
|
||||
[Flags]
|
||||
public enum RaycastFlags
|
||||
{
|
||||
None = 0,
|
||||
TelepathicCandidate = 1 << 0,
|
||||
ProximityInteract = 1 << 1,
|
||||
RayInteract = 1 << 2,
|
||||
CohtmlInteract = 1 << 3,
|
||||
All = ~0
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Constants
|
||||
|
||||
private const float MAX_RAYCAST_DISTANCE = 100f; // Max distance you can raycast
|
||||
private const float RAYCAST_SPHERE_RADIUS = 0.1f; // Radius of the proximity sphere
|
||||
private const float TELEPATHIC_SPHERE_RADIUS = 0.3f; // Radius of the telepathic sphere
|
||||
private const float MAX_TELEPATHIC_DISTANCE = 20f; // Max distance for telepathic grab
|
||||
private const int MAX_RAYCAST_HITS = 100; // Hit buffer size, high due to triggers, which we use lots in CCK
|
||||
|
||||
// Global setting is Collide, but better to be explicit about what we need
|
||||
private const QueryTriggerInteraction _triggerInteraction = QueryTriggerInteraction.Collide;
|
||||
|
||||
// Layers that are reserved for other purposes or illegal to interact with
|
||||
private const int RESERVED_OR_ILLEGAL_LAYERS = (1 << CVRLayers.IgnoreRaycast)
|
||||
| (1 << CVRLayers.MirrorReflection)
|
||||
| (1 << CVRLayers.PlayerLocal);
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Static Fields
|
||||
|
||||
private static readonly RaycastHit[] _hits = new RaycastHit[MAX_RAYCAST_HITS];
|
||||
private static readonly Comparer<RaycastHit> _hitsComparer = Comparer<RaycastHit>.Create((hit1, hit2) =>
|
||||
{
|
||||
bool isUI1 = hit1.collider.gameObject.layer == CVRLayers.UIInternal;
|
||||
bool isUI2 = hit2.collider.gameObject.layer == CVRLayers.UIInternal;
|
||||
|
||||
// Prioritize UIInternal hits
|
||||
if (isUI1 && !isUI2) return -1; // UIInternal comes first
|
||||
if (!isUI1 && isUI2) return 1; // Non-UIInternal comes after
|
||||
|
||||
// If both are UIInternal or both are not, sort by distance
|
||||
return hit1.distance.CompareTo(hit2.distance);
|
||||
});
|
||||
|
||||
private static readonly LayerMask _telepathicLayerMask = 1 << CVRLayers.MirrorReflection;
|
||||
|
||||
// Working variables to avoid repeated allocations
|
||||
private static Collider _workingCollider;
|
||||
private static GameObject _workingGameObject;
|
||||
private static Pickupable _workingPickupable;
|
||||
private static Interactable _workingInteractable;
|
||||
private static Selectable _workingSelectable;
|
||||
private static ICanvasElement _workingCanvasElement;
|
||||
|
||||
#endregion Static Fields
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private LayerMask _layerMask; // Default to no layers so we know if we fucked up
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Constructor
|
||||
|
||||
protected CVRPlayerRaycaster(Transform rayOrigin) => _rayOrigin = rayOrigin;
|
||||
protected readonly Transform _rayOrigin;
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SetLayerMask(LayerMask layerMask)
|
||||
{
|
||||
layerMask &= ~RESERVED_OR_ILLEGAL_LAYERS;
|
||||
_layerMask = layerMask;
|
||||
}
|
||||
|
||||
public CVRRaycastResult GetRaycastResults(RaycastFlags flags = RaycastFlags.All)
|
||||
{
|
||||
// Early out if we don't want to do anything
|
||||
if (flags == RaycastFlags.None) return default;
|
||||
|
||||
Ray ray = GetRayFromImpl();
|
||||
CVRRaycastResult result = new();
|
||||
|
||||
// Always check COHTML first
|
||||
if ((flags & RaycastFlags.CohtmlInteract) != 0
|
||||
&& TryProcessCohtmlHit(ray, ref result))
|
||||
return result;
|
||||
|
||||
// Check if there are pickups or interactables in immediate proximity
|
||||
if ((flags & RaycastFlags.ProximityInteract) != 0)
|
||||
{
|
||||
ProcessProximityHits(ray, ref result); // TODO: Offset origin to center of palm based on hand type
|
||||
if (result.isProximityHit)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for regular raycast hits
|
||||
if ((flags & RaycastFlags.RayInteract) != 0)
|
||||
ProcessRaycastHits(ray, ref result);
|
||||
|
||||
// If we hit something, check for telepathic grab candidates at the hit point
|
||||
if ((flags & RaycastFlags.TelepathicCandidate) != 0 && result.hit.collider)
|
||||
ProcessTelepathicGrabCandidate(result.hit.point, ref result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static bool TryProcessCohtmlHit(Ray ray, ref CVRRaycastResult result)
|
||||
{
|
||||
CohtmlControlledView hitView = CohtmlViewInputHandler.Instance.RayToView(ray,
|
||||
out float _, out Vector2 hitCoords);
|
||||
if (hitView == null) return false;
|
||||
|
||||
result.hitCohtml = true;
|
||||
result.hitCohtmlView = hitView;
|
||||
result.hitCohtmlCoords = hitCoords;
|
||||
|
||||
// Manually check for pickups & interactables on the hit view (future-proofing for menu grabbing)
|
||||
if (hitView.TryGetComponent(out _workingInteractable)) result.hitInteractable = _workingInteractable;
|
||||
if (hitView.TryGetComponent(out _workingPickupable)) result.hitPickupable = _workingPickupable;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProcessProximityHits(Ray ray, ref CVRRaycastResult result)
|
||||
{
|
||||
int proximityHits = Physics.SphereCastNonAlloc(
|
||||
ray.origin,
|
||||
RAYCAST_SPHERE_RADIUS,
|
||||
Vector3.up,
|
||||
_hits,
|
||||
0.001f,
|
||||
_layerMask,
|
||||
_triggerInteraction
|
||||
);
|
||||
|
||||
if (proximityHits <= 0) return;
|
||||
|
||||
Array.Sort(_hits, 0, proximityHits, _hitsComparer);
|
||||
|
||||
for (int i = 0; i < proximityHits; i++)
|
||||
{
|
||||
RaycastHit hit = _hits[i];
|
||||
_workingCollider = hit.collider;
|
||||
_workingGameObject = _workingCollider.gameObject;
|
||||
|
||||
// Skip things behind the ray origin
|
||||
if (Vector3.Dot(ray.direction, hit.point - ray.origin) < 0)
|
||||
continue;
|
||||
|
||||
// Check for interactables & pickupables in proximity
|
||||
if (!TryProcessInteractables(hit, ref result))
|
||||
continue;
|
||||
|
||||
result.isProximityHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessRaycastHits(Ray ray, ref CVRRaycastResult result)
|
||||
{
|
||||
// Get all hits including triggers, sorted by UI Internal layer & distance
|
||||
int hitCount = Physics.RaycastNonAlloc(ray,
|
||||
_hits,
|
||||
MAX_RAYCAST_DISTANCE,
|
||||
_layerMask,
|
||||
_triggerInteraction);
|
||||
|
||||
if (hitCount <= 0) return;
|
||||
|
||||
Array.Sort(_hits, 0, hitCount, _hitsComparer);
|
||||
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
RaycastHit hit = _hits[i];
|
||||
_workingCollider = hit.collider;
|
||||
_workingGameObject = _workingCollider.gameObject;
|
||||
|
||||
// Special case where we only get the closest water hit position.
|
||||
// As the array is sorted by distance, we only need to check if we didn't hit water yet.
|
||||
if (!result.hitWater) TryProcessFluidVolume(hit, ref result);
|
||||
|
||||
// Check for hits in order of priority
|
||||
|
||||
if (TryProcessSelectable(hit, ref result))
|
||||
break; // Hit a Unity UI Selectable (Button, Slider, etc.)
|
||||
|
||||
if (TryProcessCanvasElement(hit, ref result))
|
||||
break; // Hit a Unity UI Canvas Element (ScrollRect, idk what else yet)
|
||||
|
||||
if (TryProcessInteractables(hit, ref result))
|
||||
break; // Hit an in-range Interactable or Pickup
|
||||
|
||||
if (TryProcessWorldHit(hit, ref result))
|
||||
break; // Hit a non-trigger collider (world, end of ray)
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessTelepathicGrabCandidate(Vector3 hitPoint, ref CVRRaycastResult result)
|
||||
{
|
||||
// If we already hit a pickupable, we don't need to check for telepathic grab candidates
|
||||
if (result.hitPickupable)
|
||||
{
|
||||
result.hasTelepathicGrabCandidate = true;
|
||||
result.telepathicPickupable = result.hitPickupable;
|
||||
result.telepathicGrabPoint = hitPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the hit distance is too far, don't bother checking for telepathic grab candidates
|
||||
if (Vector3.Distance(hitPoint, _rayOrigin.position) > MAX_TELEPATHIC_DISTANCE)
|
||||
return;
|
||||
|
||||
// Check for mirror reflection triggers in a sphere around the hit point
|
||||
int telepathicHits = Physics.SphereCastNonAlloc(
|
||||
hitPoint,
|
||||
TELEPATHIC_SPHERE_RADIUS,
|
||||
Vector3.up,
|
||||
_hits,
|
||||
0.001f,
|
||||
_telepathicLayerMask,
|
||||
QueryTriggerInteraction.Collide
|
||||
);
|
||||
|
||||
if (telepathicHits <= 0) return;
|
||||
|
||||
// Look for pickupable objects near our hit point
|
||||
var nearestDistance = float.MaxValue;
|
||||
for (int i = 0; i < telepathicHits; i++)
|
||||
{
|
||||
RaycastHit hit = _hits[i];
|
||||
_workingCollider = hit.collider;
|
||||
// _workingGameObject = _workingCollider.gameObject;
|
||||
|
||||
Transform parentTransform = _workingCollider.transform.parent;
|
||||
if (!parentTransform
|
||||
|| !parentTransform.TryGetComponent(out _workingPickupable)
|
||||
|| !_workingPickupable.CanPickup)
|
||||
continue;
|
||||
|
||||
var distance = Vector3.Distance(hitPoint, hit.point);
|
||||
if (!(distance < nearestDistance))
|
||||
continue;
|
||||
|
||||
result.hasTelepathicGrabCandidate = true;
|
||||
result.telepathicPickupable = _workingPickupable;
|
||||
result.telepathicGrabPoint = hitPoint;
|
||||
nearestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryProcessSelectable(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (!_workingGameObject.TryGetComponent(out _workingSelectable))
|
||||
return false;
|
||||
|
||||
result.hitUnityUi = true;
|
||||
result.hitSelectable = _workingSelectable;
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryProcessCanvasElement(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (!_workingGameObject.TryGetComponent(out _workingCanvasElement))
|
||||
return false;
|
||||
|
||||
result.hitUnityUi = true;
|
||||
result.hitCanvasElement = _workingCanvasElement;
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TryProcessFluidVolume(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (_workingGameObject.layer != CVRLayers.Water) return;
|
||||
|
||||
result.hitWater = true;
|
||||
result.waterHit = hit;
|
||||
}
|
||||
|
||||
private static bool TryProcessInteractables(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
bool hitValidComponent = false;
|
||||
|
||||
if (_workingGameObject.TryGetComponent(out _workingInteractable)
|
||||
&& _workingInteractable.CanInteract
|
||||
&& IsCVRInteractableWithinRange(_workingInteractable, hit))
|
||||
{
|
||||
result.hitInteractable = _workingInteractable;
|
||||
hitValidComponent = true;
|
||||
}
|
||||
if (_workingGameObject.TryGetComponent(out _workingPickupable)
|
||||
&& _workingPickupable.CanPickup
|
||||
&& IsCVRPickupableWithinRange(_workingPickupable, hit))
|
||||
{
|
||||
result.hitPickupable = _workingPickupable;
|
||||
hitValidComponent = true;
|
||||
}
|
||||
|
||||
if (!hitValidComponent)
|
||||
return false;
|
||||
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryProcessWorldHit(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (_workingCollider.isTrigger)
|
||||
return false;
|
||||
|
||||
result.hitWorld = true;
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected abstract Ray GetRayFromImpl();
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Utility Because Original Methods Are Broken
|
||||
|
||||
private static bool IsCVRInteractableWithinRange(Interactable interactable, RaycastHit hit)
|
||||
{
|
||||
if (interactable is not CVRInteractable cvrInteractable)
|
||||
return true;
|
||||
|
||||
foreach (CVRInteractableAction action in cvrInteractable.actions)
|
||||
{
|
||||
if (action.actionType
|
||||
is not (CVRInteractableAction.ActionRegister.OnInteractDown
|
||||
or CVRInteractableAction.ActionRegister.OnInteractUp
|
||||
or CVRInteractableAction.ActionRegister.OnInputDown
|
||||
or CVRInteractableAction.ActionRegister.OnInputUp))
|
||||
continue;
|
||||
|
||||
float maxDistance = action.floatVal;
|
||||
if (Mathf.Approximately(maxDistance, 0f)
|
||||
|| hit.distance <= maxDistance)
|
||||
return true; // Interactable is within range
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsCVRPickupableWithinRange(Pickupable pickupable, RaycastHit hit)
|
||||
{
|
||||
return hit.distance <= pickupable.MaxGrabDistance;
|
||||
}
|
||||
|
||||
private static bool IsCVRCanvasWrapperWithinRange(CVRCanvasWrapper canvasWrapper, RaycastHit hit)
|
||||
{
|
||||
return hit.distance <= canvasWrapper.MaxInteractDistance;
|
||||
}
|
||||
|
||||
#endregion Utility Because Original Methods Are Broken
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public class CVRPlayerRaycasterMouse : CVRPlayerRaycaster
|
||||
{
|
||||
private readonly Camera _camera;
|
||||
public CVRPlayerRaycasterMouse(Transform rayOrigin, Camera camera) : base(rayOrigin) { _camera = camera; }
|
||||
protected override Ray GetRayFromImpl() => Cursor.lockState == CursorLockMode.Locked
|
||||
? new Ray(_camera.transform.position, _camera.transform.forward)
|
||||
: _camera.ScreenPointToRay(Input.mousePosition);
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public class CVRPlayerRaycasterTransform : CVRPlayerRaycaster
|
||||
{
|
||||
public CVRPlayerRaycasterTransform(Transform rayOrigin) : base(rayOrigin) { }
|
||||
protected override Ray GetRayFromImpl() => new(_rayOrigin.position, _rayOrigin.forward);
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.UI;
|
||||
using NAK.SuperAwesomeMod.Components;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public struct CVRRaycastResult
|
||||
{
|
||||
// Hit flags
|
||||
public bool hitWorld; // Any non-specific collision
|
||||
public bool hitWater; // Hit a fluid volume
|
||||
public bool hitCohtml; // Specifically hit a COHTML view (Main/Quick Menu)
|
||||
public bool isProximityHit; // Hit was from proximity sphere check
|
||||
public bool hitUnityUi; // Hit a canvas
|
||||
|
||||
// Main raycast hit info
|
||||
public RaycastHit hit;
|
||||
public RaycastHit? waterHit; // Only valid if hitWater is true
|
||||
|
||||
// Specific hit components
|
||||
public Pickupable hitPickupable;
|
||||
public Interactable hitInteractable;
|
||||
public Selectable hitSelectable;
|
||||
public ICanvasElement hitCanvasElement;
|
||||
|
||||
// COHTML specific results
|
||||
public CohtmlControlledView hitCohtmlView;
|
||||
public Vector2 hitCohtmlCoords;
|
||||
|
||||
// Telepathic pickup
|
||||
public bool hasTelepathicGrabCandidate;
|
||||
public Pickupable telepathicPickupable;
|
||||
public Vector3 telepathicGrabPoint;
|
||||
}
|
||||
}
|
|
@ -1,312 +0,0 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public class CVRRaycastDebugManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static CVRRaycastDebugManager _instance;
|
||||
public static CVRRaycastDebugManager Instance => _instance;
|
||||
|
||||
public static void Initialize(Camera camera)
|
||||
{
|
||||
if (_instance != null) return;
|
||||
|
||||
var go = new GameObject("RaycastDebugManager");
|
||||
_instance = go.AddComponent<CVRRaycastDebugManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
|
||||
_instance.Setup(camera);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private CVRPlayerRaycasterMouse _raycaster;
|
||||
private CVRRaycastResult _lastResult;
|
||||
private System.Diagnostics.Stopwatch _stopwatch;
|
||||
|
||||
// Performance tracking
|
||||
private const int ROLLING_AVERAGE_SAMPLES = 60; // 1 second at 60fps
|
||||
private readonly float[] _timeHistory = new float[ROLLING_AVERAGE_SAMPLES];
|
||||
private int _currentSampleIndex;
|
||||
private float _lastRaycastTime;
|
||||
private float _minRaycastTime = float.MaxValue;
|
||||
private float _maxRaycastTime;
|
||||
private float _rollingAverageTime;
|
||||
private bool _historyFilled;
|
||||
|
||||
private const int DEBUG_PANEL_WIDTH = 300;
|
||||
private const int DEBUG_PANEL_MARGIN = 10;
|
||||
private const float MOUSE_CURSOR_SIZE = 24f;
|
||||
private const float CURSOR_OFFSET = MOUSE_CURSOR_SIZE / 2f;
|
||||
|
||||
private GUIStyle _labelStyle;
|
||||
private GUIStyle _headerStyle;
|
||||
private GUIStyle _boxStyle;
|
||||
|
||||
private static readonly Color32 TIMING_COLOR = new(255, 255, 150, 255); // Yellow
|
||||
private static readonly Color32 COHTML_COLOR = new(150, 255, 150, 255); // Green
|
||||
private static readonly Color32 UI_COLOR = new(150, 150, 255, 255); // Blue
|
||||
private static readonly Color32 UNITY_UI_COLOR = new(255, 200, 150, 255); // Orange
|
||||
private static readonly Color32 INTERACT_COLOR = new(255, 150, 150, 255); // Red
|
||||
private static readonly Color32 WATER_COLOR = new(150, 255, 255, 255); // Cyan
|
||||
private static readonly Color32 TELEPATHIC_COLOR = new(255, 150, 255, 255);// Purple
|
||||
private static readonly Color32 SELECTABLE_COLOR = new(200, 150, 255, 255);// Light Purple
|
||||
|
||||
#endregion
|
||||
|
||||
#region Setup
|
||||
|
||||
private void Setup(Camera camera)
|
||||
{
|
||||
_raycaster = new CVRPlayerRaycasterMouse(transform, camera);
|
||||
_raycaster.SetLayerMask(Physics.DefaultRaycastLayers);
|
||||
_stopwatch = new System.Diagnostics.Stopwatch();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MonoBehaviour
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
_lastResult = _raycaster.GetRaycastResults();
|
||||
_stopwatch.Stop();
|
||||
|
||||
UpdatePerformanceMetrics();
|
||||
}
|
||||
|
||||
private void UpdatePerformanceMetrics()
|
||||
{
|
||||
// Calculate current frame time
|
||||
_lastRaycastTime = _stopwatch.ElapsedTicks / (float)System.TimeSpan.TicksPerMillisecond;
|
||||
|
||||
// Update min/max
|
||||
_minRaycastTime = Mathf.Min(_minRaycastTime, _lastRaycastTime);
|
||||
_maxRaycastTime = Mathf.Max(_maxRaycastTime, _lastRaycastTime);
|
||||
|
||||
// Update rolling average
|
||||
_timeHistory[_currentSampleIndex] = _lastRaycastTime;
|
||||
|
||||
// Calculate rolling average based on filled samples
|
||||
float sum = 0f;
|
||||
int sampleCount = _historyFilled ? ROLLING_AVERAGE_SAMPLES : _currentSampleIndex + 1;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
sum += _timeHistory[i];
|
||||
|
||||
_rollingAverageTime = sum / sampleCount;
|
||||
|
||||
// Update index for next frame
|
||||
_currentSampleIndex = (_currentSampleIndex + 1) % ROLLING_AVERAGE_SAMPLES;
|
||||
if (_currentSampleIndex == 0)
|
||||
_historyFilled = true;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
InitializeStyles();
|
||||
DrawDebugPanel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drawing Methods
|
||||
|
||||
private void InitializeStyles()
|
||||
{
|
||||
if (_labelStyle != null) return;
|
||||
|
||||
_labelStyle = new GUIStyle
|
||||
{
|
||||
normal = { textColor = Color.white },
|
||||
fontSize = 12,
|
||||
padding = new RectOffset(5, 5, 2, 2),
|
||||
margin = new RectOffset(5, 5, 0, 0)
|
||||
};
|
||||
|
||||
_headerStyle = new GUIStyle
|
||||
{
|
||||
normal = { textColor = Color.white },
|
||||
fontSize = 14,
|
||||
fontStyle = FontStyle.Bold,
|
||||
padding = new RectOffset(5, 5, 5, 5),
|
||||
margin = new RectOffset(5, 5, 5, 5)
|
||||
};
|
||||
|
||||
_boxStyle = new GUIStyle(GUI.skin.box)
|
||||
{
|
||||
padding = new RectOffset(10, 10, 5, 5),
|
||||
margin = new RectOffset(5, 5, 5, 5)
|
||||
};
|
||||
}
|
||||
|
||||
private void DrawDebugPanel()
|
||||
{
|
||||
var rect = new Rect(
|
||||
Screen.width - DEBUG_PANEL_WIDTH - DEBUG_PANEL_MARGIN,
|
||||
DEBUG_PANEL_MARGIN,
|
||||
DEBUG_PANEL_WIDTH,
|
||||
Screen.height - (DEBUG_PANEL_MARGIN * 2)
|
||||
);
|
||||
|
||||
GUI.Box(rect, "");
|
||||
GUILayout.BeginArea(rect);
|
||||
|
||||
GUI.backgroundColor = Color.black;
|
||||
GUILayout.Label("Raycast Debug Info", _headerStyle);
|
||||
|
||||
DrawPerformanceSection();
|
||||
DrawCohtmlSection();
|
||||
DrawUnityUISection();
|
||||
DrawSelectableSection();
|
||||
DrawInteractionSection();
|
||||
DrawWaterSection();
|
||||
DrawTelepathicSection();
|
||||
DrawWorldHitSection();
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void DrawPerformanceSection()
|
||||
{
|
||||
GUI.backgroundColor = TIMING_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Performance", _headerStyle);
|
||||
DrawLabel("Last Raycast", $"{_lastRaycastTime:F3} ms");
|
||||
DrawLabel("Average (1s)", $"{_rollingAverageTime:F3} ms");
|
||||
DrawLabel("Min", $"{_minRaycastTime:F3} ms");
|
||||
DrawLabel("Max", $"{_maxRaycastTime:F3} ms");
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawCohtmlSection()
|
||||
{
|
||||
if (!_lastResult.hitCohtml) return;
|
||||
|
||||
GUI.backgroundColor = COHTML_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("COHTML Hit", _headerStyle);
|
||||
DrawLabel("View", _lastResult.hitCohtmlView.name);
|
||||
DrawLabel("Coords", _lastResult.hitCohtmlCoords.ToString());
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawUnityUISection()
|
||||
{
|
||||
if (!_lastResult.hitUnityUi || _lastResult.hitCanvasElement == null) return;
|
||||
|
||||
GUI.backgroundColor = UNITY_UI_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Unity UI Hit", _headerStyle);
|
||||
|
||||
var canvasElement = _lastResult.hitCanvasElement;
|
||||
var gameObject = canvasElement as MonoBehaviour;
|
||||
|
||||
DrawLabel("Canvas Element", gameObject != null ? gameObject.name : "Unknown");
|
||||
DrawLabel("Element Type", canvasElement.GetType().Name);
|
||||
|
||||
if (gameObject != null)
|
||||
{
|
||||
DrawLabel("GameObject", gameObject.gameObject.name);
|
||||
|
||||
if (gameObject.transform.parent != null)
|
||||
DrawLabel("Parent", gameObject.transform.parent.name);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawSelectableSection()
|
||||
{
|
||||
if (_lastResult.hitSelectable == null) return;
|
||||
|
||||
GUI.backgroundColor = SELECTABLE_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("UI Selectable", _headerStyle);
|
||||
DrawLabel("Selectable", _lastResult.hitSelectable.name);
|
||||
DrawLabel("Selectable Type", _lastResult.hitSelectable.GetType().Name);
|
||||
DrawLabel("Is Interactable", _lastResult.hitSelectable.interactable.ToString());
|
||||
DrawLabel("Navigation Mode", _lastResult.hitSelectable.navigation.mode.ToString());
|
||||
|
||||
if (_lastResult.hitSelectable is Toggle toggle)
|
||||
DrawLabel("Toggle State", toggle.isOn.ToString());
|
||||
else if (_lastResult.hitSelectable is Slider slider)
|
||||
DrawLabel("Slider Value", slider.value.ToString("F2"));
|
||||
else if (_lastResult.hitSelectable is Scrollbar scrollbar)
|
||||
DrawLabel("Scrollbar Value", scrollbar.value.ToString("F2"));
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawInteractionSection()
|
||||
{
|
||||
if (!_lastResult.hitPickupable && !_lastResult.hitInteractable) return;
|
||||
|
||||
GUI.backgroundColor = INTERACT_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Interaction", _headerStyle);
|
||||
if (_lastResult.hitPickupable)
|
||||
DrawLabel("Pickupable", _lastResult.hitPickupable.name);
|
||||
if (_lastResult.hitInteractable)
|
||||
DrawLabel("Interactable", _lastResult.hitInteractable.name);
|
||||
DrawLabel("Is Proximity", _lastResult.isProximityHit.ToString());
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawWaterSection()
|
||||
{
|
||||
if (!_lastResult.hitWater || !_lastResult.waterHit.HasValue) return;
|
||||
|
||||
GUI.backgroundColor = WATER_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Water Surface", _headerStyle);
|
||||
DrawLabel("Hit Point", _lastResult.waterHit.Value.point.ToString("F2"));
|
||||
DrawLabel("Surface Normal", _lastResult.waterHit.Value.normal.ToString("F2"));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawTelepathicSection()
|
||||
{
|
||||
if (!_lastResult.hasTelepathicGrabCandidate) return;
|
||||
|
||||
GUI.backgroundColor = TELEPATHIC_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Telepathic Grab", _headerStyle);
|
||||
DrawLabel("Target", _lastResult.telepathicPickupable.name);
|
||||
DrawLabel("Grab Point", _lastResult.telepathicGrabPoint.ToString("F2"));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawWorldHitSection()
|
||||
{
|
||||
if (_lastResult.hitCohtml ||
|
||||
_lastResult.hitPickupable ||
|
||||
_lastResult.hitInteractable ||
|
||||
_lastResult.hitUnityUi ||
|
||||
!_lastResult.hitWorld ||
|
||||
_lastResult.hit.collider == null) return;
|
||||
|
||||
GUI.backgroundColor = Color.grey;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("World Hit", _headerStyle);
|
||||
DrawLabel("Object", _lastResult.hit.collider.name);
|
||||
DrawLabel("Distance", _lastResult.hit.distance.ToString("F2"));
|
||||
DrawLabel("Point", _lastResult.hit.point.ToString("F2"));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawLabel(string label, string value)
|
||||
{
|
||||
GUILayout.Label($"{label}: {value}", _labelStyle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core.Base.Jobs;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.Interaction.RaycastImpl;
|
||||
using ABI_RC.Core.Util.AssetFiltering;
|
||||
using ABI.CCK.Components;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using NAK.SuperAwesomeMod.Components;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace NAK.SuperAwesomeMod;
|
||||
|
||||
public class SuperAwesomeModMod : MelonMod
|
||||
{
|
||||
#region Melon Events
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnPlayerSetupStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(SceneLoaded).GetMethod(nameof(SceneLoaded.FilterWorldComponent),
|
||||
BindingFlags.NonPublic | BindingFlags.Static),
|
||||
postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnShitLoaded),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
// patch SharedFilter.ProcessCanvas
|
||||
HarmonyInstance.Patch(
|
||||
typeof(SharedFilter).GetMethod(nameof(SharedFilter.ProcessCanvas),
|
||||
BindingFlags.Public | BindingFlags.Static),
|
||||
postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnProcessCanvas),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
LoggerInstance.Msg("SuperAwesomeModMod! OnInitializeMelon! :D");
|
||||
}
|
||||
|
||||
public override void OnApplicationQuit()
|
||||
{
|
||||
LoggerInstance.Msg("SuperAwesomeModMod! OnApplicationQuit! D:");
|
||||
}
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
private static void OnPlayerSetupStart()
|
||||
{
|
||||
CVRRaycastDebugManager.Initialize(PlayerSetup.Instance.desktopCam);
|
||||
}
|
||||
|
||||
private static void OnShitLoaded(Component c, List<Task> asyncTasks = null, Scene? scene = null)
|
||||
{
|
||||
if (c == null)
|
||||
return;
|
||||
|
||||
if (c.gameObject == null)
|
||||
return;
|
||||
|
||||
if (c.gameObject.scene.buildIndex > 0)
|
||||
return;
|
||||
|
||||
if ((scene != null)
|
||||
&& (c.gameObject.scene != scene))
|
||||
return;
|
||||
|
||||
if (c is Canvas canvas) canvas.gameObject.AddComponent<CVRCanvasWrapper>();
|
||||
}
|
||||
|
||||
private static void OnProcessCanvas(string collectionId, Canvas canvas)
|
||||
{
|
||||
canvas.gameObject.AddComponent<CVRCanvasWrapper>();
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
# ASTExtension
|
||||
|
||||
Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):
|
||||
- VR Gesture to scale
|
||||
- Persistent height
|
||||
- Copy height from others
|
||||
|
||||
Best used with Avatar Scale Tool, but will attempt to work with found scaling setups.
|
||||
Requires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.
|
||||
|
||||
## Supported Setups
|
||||
|
||||
ASTExtension will attempt to work with the following setups:
|
||||
|
||||
**Parameter Names:**
|
||||
- AvatarScale
|
||||
- Scale
|
||||
- Scaler
|
||||
- Scale/Scale
|
||||
- Height
|
||||
- LoliModifier
|
||||
- AvatarSize
|
||||
- Size
|
||||
- SizeScale
|
||||
- Scaling
|
||||
|
||||
These parameter names are not case sensitive and have been gathered from polling the community for common parameter names.
|
||||
|
||||
Assuming the parameter is a float, ASTExtension will attempt to use it as the height parameter. Will automatically calibrate to the height range of the found parameter, assuming the scaling animation is in a blend tree / state using motion time & is linear. The scaling animation state **must be active** at time of avatar load.
|
||||
|
||||
The max value ASTExtension will drive the parameter to is 100. As the mod is having to guess the max height, it may not be accurate if the max height is not capped at a multiple of 10.
|
||||
|
||||
Examples:
|
||||
- `AvatarScale` - 0 to 1 (slider)
|
||||
- This is the default setup for Avatar Scale Tool and will work perfectly.
|
||||
- `Scale` - 0 to 100 (input single)
|
||||
- This will also work perfectly as the max height is a multiple of 10.
|
||||
- `Height` - 0 to 2 (input single)
|
||||
- This will not work properly. The max value to drive the parameter to is not a multiple of 10, and as such ASTExtension will believe the parameter range is 0 to 1.
|
||||
- `BurntToast` - 0 to 10 (input single)
|
||||
- This will not work properly. The parameter name is not recognized by ASTExtension.
|
||||
|
||||
If your setup is theoretically supported but not working, it is likely the scaling animation is not linear or has loop enabled if using Motion Time, making the first and last frame identical height. In this case, you will need to fix your animation clip curves / blend tree to be linear &|| not loop, or use Avatar Scale Tool to generate a new scaling animation.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation 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.
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"_id": 223,
|
||||
"name": "ASTExtension",
|
||||
"modversion": "1.0.2",
|
||||
"gameversion": "2025r178",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.",
|
||||
"searchtags": [
|
||||
"tool",
|
||||
"scaling",
|
||||
"height",
|
||||
"extension",
|
||||
"avatar"
|
||||
],
|
||||
"requirements": [
|
||||
"BTKUILib"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/ASTExtension.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/",
|
||||
"changelog": "- Fixes for 2025r178",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.WhereAmIPointing;
|
||||
|
||||
public class WhereAmIPointingMod : MelonMod
|
||||
{
|
||||
#region Melon Preferences
|
||||
|
||||
// cannot disable because then id need extra logic to reset the alpha :)
|
||||
// private const string SettingsCategory = nameof(WhereAmIPointingMod);
|
||||
//
|
||||
// private static readonly MelonPreferences_Category Category =
|
||||
// MelonPreferences.CreateCategory(SettingsCategory);
|
||||
//
|
||||
// private static readonly MelonPreferences_Entry<bool> Entry_Enabled =
|
||||
// Category.CreateEntry("enabled", true, display_name: "Enabled",description: "Toggle WhereAmIPointingMod entirely.");
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
ApplyPatches(typeof(ControllerRay_Patches));
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
#region Patches
|
||||
|
||||
private static class ControllerRay_Patches
|
||||
{
|
||||
private const float ORIGINAL_ALPHA = 0.502f;
|
||||
private const float INTERACTION_ALPHA = 0.1f;
|
||||
private const float RAY_LENGTH = 1000f; // game normally raycasts to PositiveInfinity... -_-
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.LateUpdate))]
|
||||
private static void Postfix_ControllerRay_LateUpdate(ref ControllerRay __instance)
|
||||
{
|
||||
if (__instance.isDesktopRay
|
||||
|| !__instance.enabled
|
||||
|| !__instance.IsTracking()
|
||||
|| !__instance.lineRenderer)
|
||||
return;
|
||||
|
||||
UpdateLineRendererAlpha(__instance);
|
||||
|
||||
if (__instance.lineRenderer.enabled
|
||||
|| !ShouldOverrideLineRenderer(__instance))
|
||||
return;
|
||||
|
||||
UpdateLineRendererPosition(__instance);
|
||||
}
|
||||
|
||||
private static void UpdateLineRendererAlpha(ControllerRay instance)
|
||||
{
|
||||
Material material = instance.lineRenderer.material;
|
||||
Color color = material.color;
|
||||
|
||||
bool anyMenuOpen = ViewManager.Instance.IsAnyMenuOpen;
|
||||
float targetAlpha = (!anyMenuOpen || instance.uiActive) ? ORIGINAL_ALPHA : INTERACTION_ALPHA;
|
||||
if (!(Math.Abs(color.a - targetAlpha) > float.Epsilon))
|
||||
return;
|
||||
|
||||
color.a = targetAlpha;
|
||||
material.color = color;
|
||||
}
|
||||
|
||||
private static bool ShouldOverrideLineRenderer(ControllerRay instance)
|
||||
{
|
||||
if (!ViewManager.Instance.IsAnyMenuOpen)
|
||||
return false;
|
||||
|
||||
if (CVR_MenuManager.Instance.IsQuickMenuOpen
|
||||
&& instance.hand == CVR_MenuManager.Instance.SelectedQuickMenuHand)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void UpdateLineRendererPosition(ControllerRay instance)
|
||||
{
|
||||
Vector3 rayOrigin = instance.rayDirectionTransform.position;
|
||||
Vector3 rayEnd = rayOrigin + instance.rayDirectionTransform.forward * RAY_LENGTH;
|
||||
|
||||
instance.lineRenderer.SetPosition(0, instance.lineRenderer.transform.InverseTransformPoint(rayOrigin));
|
||||
instance.lineRenderer.SetPosition(1, instance.lineRenderer.transform.InverseTransformPoint(rayEnd));
|
||||
instance.lineRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Patches
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using NAK.WhereAmIPointing.Properties;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.WhereAmIPointing))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.WhereAmIPointing))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.WhereAmIPointing.WhereAmIPointingMod),
|
||||
nameof(NAK.WhereAmIPointing),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "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.WhereAmIPointing.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.1";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue