Compare commits

..

No commits in common. "6249696efaa00151708201b07259ea5cd5284e4d" and "5e822cec8d5c9faeb8ccb1c98e044be32d30f1da" have entirely different histories.

645 changed files with 3798 additions and 5463 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
namespace Popcron;
public class Constants
{
public const string UniqueIdentifier = "Popcron.Gizmos";
public const string EnabledKey = UniqueIdentifier + ".Enabled";
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>SpawnableReceiveOwnChanges</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -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);
}
}
}

View file

@ -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";
}

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>LoadedObjectHack</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -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;
}
}
}

View file

@ -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";
}

View file

@ -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"
}

View file

@ -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
}

View file

@ -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
}

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk" />

View file

@ -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.");
}
}

View file

@ -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";
}

View file

@ -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.

View file

@ -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"
}

View file

@ -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.

View file

@ -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);
}
}

View file

@ -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
}

View file

@ -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;
// }
}

View file

@ -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";
}

View file

@ -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.

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>LoadedObjectHack</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -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"
}

View file

@ -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.

View file

@ -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"
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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>();
}
}

View file

@ -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.

View file

@ -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"
}

View file

@ -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
}

View file

@ -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";
}

View file

@ -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