Compare commits

...

37 commits

Author SHA1 Message Date
NotAKidoS
6249696efa [RelativeSync] Updated format.json 2025-04-03 05:08:57 -05:00
NotAKidoS
ea5a9eef97 [RelativeSync] Fixed execution order of RelativeSyncController & PuppetMaster.ProcessAvatarVisibility, so moving at high speeds with passengers does not disrupt voice or avatar distance hider 2025-04-03 05:08:25 -05:00
NotAKidoS
063669e8a6 [SmootherRay] Update format.json 2025-04-03 04:40:20 -05:00
NotAKidoS
9133c6b161 [ShareBubbles] Remove todo notes 2025-04-03 04:34:30 -05:00
NotAKidoS
84dcf35362 [ScrollFlight] Update format.json 2025-04-03 04:19:17 -05:00
NotAKidoS
0fdbcdec34 [ShareBubbles] Updated format.json 2025-04-03 04:16:01 -05:00
NotAKidoS
72b690365b [ShareBubbles] Fixed Public/Private text on bubble not being correct, Fixed bubble displaying as locked or not claimed despite being shared the content if it was private and not owned by you 2025-04-03 04:15:11 -05:00
NotAKidoS
e540628db1 [RCCVirtualSteeringWheel] Updated format.json 2025-04-03 04:12:04 -05:00
NotAKidoS
018112d6b9 [RCCVirtualSteeringWheel] Fixed error spam when sitting in a CVRSeat with lockControls, but no RCC component in parent 2025-04-03 04:11:40 -05:00
NotAKidoS
138c9a9856 [RCCVirtualSteeringWheel] Fixed generated steering wheel pickup collider not being marked isTrigger 2025-04-03 04:06:43 -05:00
NotAKidoS
aad4276f88 [Stickers] Update format.json 2025-04-03 04:05:24 -05:00
NotAKidoS
a9cebb85e9 [Stickers] Fixed scrolling cycling selected sticker slot despite not being in placement mode 2025-04-03 04:04:14 -05:00
NotAKidoS
f99a22499c [Stickers] Fixed placing stickers when Cohtml text input fields were focused 2025-04-03 04:02:58 -05:00
NotAKidoS
c35d03b1ec [NAK_CVR_Mods] update sln 2025-04-03 04:01:50 -05:00
NotAKidoS
16fb070e8b [ThirdPerson] bump version 2025-04-03 04:01:37 -05:00
NotAKidoS
bf89ee24f5 [Stickers] bump version, fixes for 2025r179 2025-04-03 04:01:28 -05:00
NotAKidoS
cdcb70a4b1 [SmootherRay] bump version, fixes for 2025r179 2025-04-03 04:01:22 -05:00
NotAKidoS
697ad77f5f [ShareBubbles] bump version, fixes for 2025r179 2025-04-03 04:01:15 -05:00
NotAKidoS
940777d9e5 [ScrollFlight] bump version 2025-04-03 04:00:56 -05:00
NotAKidoS
73d76010bc [ScrollFlight] bump version 2025-04-03 04:00:48 -05:00
NotAKidoS
75de6d33a0 [RelativeSync] bump version 2025-04-03 04:00:42 -05:00
NotAKidoS
9d2c3ed244 [RCCVirtualSteeringWheel] bump version 2025-04-03 04:00:35 -05:00
NotAKidoS
bddc21ec08 [PropUndoButton] bump version 2025-04-03 04:00:19 -05:00
NotAKidoS
e24eae5a22 [PropLoadingHexagon] bump version, fix for 2025r179 2025-04-03 04:00:08 -05:00
NotAKidoS
d0504fef91 [PortableCameraAdditions] bump version 2025-04-03 03:59:40 -05:00
NotAKidoS
ef3ecde553 [PathCamDisabler] bump version 2025-04-03 03:59:29 -05:00
NotAKidoS
200e174b3f [LazyPrune] bump version 2025-04-03 03:59:19 -05:00
NotAKidoS
4b5c19676d [KeepVelocityOnExitFlight] bump version 2025-04-03 03:59:10 -05:00
NotAKidoS
c0ba230fa5 [CustomSpawnPoint] bump version 2025-04-03 03:58:57 -05:00
NotAKidoS
736dc71eec [AvatarQueueSystemTweaks] bump version 2025-04-03 03:58:46 -05:00
NotAKidoS
61f56b2f84 [ASTExtension] bump version 2025-04-03 03:58:34 -05:00
NotAKidoS
915253972d [MuteSFX] Deprecate because functionality is doable native 2025-04-03 03:31:01 -05:00
NotAKidoS
8cffe8a5be [NAK_CVR_Mods] Update solution 2025-04-03 03:16:00 -05:00
NotAKidoS
7ad549b0bb dead 2025-04-03 03:08:25 -05:00
NotAKidoS
323eb92f2e further cleanup of repo 2025-04-03 03:03:24 -05:00
NotAKidoS
4f8dcb0cd0 including mutual mute 2025-04-03 02:59:18 -05:00
NotAKidoS
0042590aa6 Move many mods to Deprecated folder, fix spelling 2025-04-03 02:57:35 -05:00
645 changed files with 5459 additions and 3794 deletions

View file

@ -0,0 +1,229 @@
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

@ -0,0 +1,304 @@
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

@ -0,0 +1,212 @@
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

@ -0,0 +1,156 @@
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

@ -0,0 +1,81 @@
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

@ -0,0 +1,147 @@
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

@ -5,9 +5,14 @@ namespace NAK.AvatarCloneTest;
public class AvatarCloneExclusion : IExclusionBehaviour
{
public readonly Dictionary<SkinnedMeshRenderer, List<int>> skinnedToBoneIndex = new();
public readonly List<Transform> affectedTransforms = new();
public readonly List<Renderer> affectedRenderers = new();
public HashSet<Transform> affectedTransformSet = new();
private readonly AvatarClone _cloneSystem;
private readonly Transform _target;
private Transform _shrinkBone;
internal Transform _shrinkBone;
public bool isImmuneToGlobalState { get; set; }
@ -19,20 +24,16 @@ public class AvatarCloneExclusion : IExclusionBehaviour
public void UpdateExclusions(bool isShown, bool shrinkToZero)
{
Debug.Log($"[AvatarClone2] Updating exclusion for {_target.name}: isShown={isShown}, shrinkToZero={shrinkToZero}");
if (_shrinkBone == null)
{
// Create shrink bone parented directly to target
_shrinkBone = new GameObject($"{_target.name}_Shrink").transform;
_shrinkBone.SetParent(_target, false);
Debug.Log($"[AvatarClone2] Created shrink bone for {_target.name}");
}
// Set scale based on shrink mode
_shrinkBone.localScale = shrinkToZero ? Vector3.zero : Vector3.positiveInfinity;
// Let the clone system handle the update
_cloneSystem.HandleExclusionUpdate(_target, _shrinkBone, isShown);
// Replace the bone references with the shrink bone for the indicies we modify
_cloneSystem.HandleExclusionUpdate(this, isShown);
}
}

View file

@ -1,4 +1,6 @@
using ABI_RC.Core;
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.Savior;
using MelonLoader;
using UnityEngine;
@ -15,17 +17,17 @@ public class AvatarCloneTestMod : MelonMod
Category.CreateEntry("use_avatar_clone_test", true,
"Use Avatar Clone", description: "Uses the Avatar Clone setup for the local avatar.");
// internal static readonly MelonPreferences_Entry<bool> EntryCopyBlendShapes =
// Category.CreateEntry("copy_blend_shapes", true,
// "Copy Blend Shapes", description: "Copies the blend shapes from the original avatar to the clone.");
//
// internal static readonly MelonPreferences_Entry<bool> EntryCopyMaterials =
// Category.CreateEntry("copy_materials", true,
// "Copy Materials", description: "Copies the materials from the original avatar to the clone.");
//
// internal static readonly MelonPreferences_Entry<bool> EntryCopyMeshes =
// Category.CreateEntry("copy_meshes", true,
// "Copy Meshes", description: "Copies the meshes from the original avatar to the clone.");
internal static readonly MelonPreferences_Entry<bool> EntryCloneMeshRenderers =
Category.CreateEntry("clone_mesh_renderers", false,
"Clone Mesh Renderers", description: "Clones the mesh renderers from the original avatar to the clone.");
internal static readonly MelonPreferences_Entry<bool> EntryCopyBlendShapes =
Category.CreateEntry("copy_blend_shapes", true,
"Copy Blend Shapes", description: "Copies the blend shapes from the original avatar to the clone.");
internal static readonly MelonPreferences_Entry<bool> EntryCopyMaterials =
Category.CreateEntry("copy_materials", true,
"Copy Materials", description: "Copies the materials from the original avatar to the clone.");
#endregion Melon Preferences
@ -49,6 +51,13 @@ public class AvatarCloneTestMod : MelonMod
}
}
}
// if pressing ctrl + r, reload avatar
if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.R))
{
var player = MetaPort.Instance.currentAvatarGuid;
AssetManagement.Instance.LoadLocalAvatar(player);
}
}
#endregion Melon Events

View file

@ -0,0 +1,22 @@
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

@ -27,6 +27,6 @@ using System.Reflection;
namespace NAK.AvatarCloneTest.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Version = "1.0.3";
public const string Author = "NotAKidoS";
}

View file

@ -211,7 +211,7 @@ public class AvatarScaleManager : MonoBehaviour
public float GetHeight()
{
if (_localAvatarScaler == null)
return PlayerAvatarPoint.defaultAvatarHeight;
return PlayerAvatarPoint.DefaultAvatarHeight;
if (!_localAvatarScaler.IsForcingHeight())
return PlayerSetup.Instance.GetAvatarHeight();
@ -222,7 +222,7 @@ public class AvatarScaleManager : MonoBehaviour
public float GetAnimationClipHeight()
{
if (_localAvatarScaler == null)
return PlayerAvatarPoint.defaultAvatarHeight;
return PlayerAvatarPoint.DefaultAvatarHeight;
if (!_localAvatarScaler.IsForcingHeight())
return PlayerSetup.Instance.GetAvatarHeight();

View file

@ -1,4 +1,5 @@
using ABI_RC.Core.Player;
using ABI_RC.Core;
using ABI_RC.Core.Player;
using ABI_RC.Core.UI;
using NAK.AvatarScaleMod.AvatarScaling;
using UnityEngine;
@ -72,7 +73,7 @@ public class LocalScaler : BaseScaler
}
// animation scale changed, record it!
Vector3 scaleDifference = PlayerSetup.DivideVectors(localScale - _initialScale, _initialScale);
Vector3 scaleDifference = CVRTools.DivideVectors(localScale - _initialScale, _initialScale);
_animatedScaleFactor = scaleDifference.y;
_animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight;
_animatedScale = localScale;

View file

@ -3,32 +3,32 @@ using BTKUILib;
using BTKUILib.UIObjects;
using NAK.AvatarScaleMod.AvatarScaling;
namespace NAK.AvatarScaleMod.Integrations
{
public static partial class BtkUiAddon
{
private static Page _asmRootPage;
private static string _rootPageElementID;
namespace NAK.AvatarScaleMod.Integrations;
public static void Initialize()
{
public static partial class BtkUiAddon
{
private static Page _asmRootPage;
private static string _rootPageElementID;
public static void Initialize()
{
Prepare_Icons();
Setup_AvatarScaleModTab();
Setup_PlayerSelectPage();
}
#region Initialization
#region Initialization
private static void Prepare_Icons()
{
private static void Prepare_Icons()
{
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightConfig",
GetIconStream("ASM_Icon_AvatarHeightConfig.png"));
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightCopy",
GetIconStream("ASM_Icon_AvatarHeightCopy.png"));
}
private static void Setup_AvatarScaleModTab()
{
private static void Setup_AvatarScaleModTab()
{
_asmRootPage = new Page(ModSettings.ModName, ModSettings.ASM_SettingsCategory, true, "ASM_Icon_AvatarHeightConfig")
{
MenuTitle = ModSettings.ASM_SettingsCategory,
@ -54,18 +54,18 @@ namespace NAK.AvatarScaleMod.Integrations
Setup_DebugOptionsCategory(_asmRootPage);
}
#endregion
#endregion
#region Player Count Display
#region Player Count Display
private static void OnWorldLeave()
=> UpdatePlayerCountDisplay();
private static void OnWorldLeave()
=> UpdatePlayerCountDisplay();
private static void OnUserJoinLeave(CVRPlayerEntity _)
=> UpdatePlayerCountDisplay();
private static void OnUserJoinLeave(CVRPlayerEntity _)
=> UpdatePlayerCountDisplay();
private static void UpdatePlayerCountDisplay()
{
private static void UpdatePlayerCountDisplay()
{
if (_asmRootPage == null)
return;
@ -74,14 +74,14 @@ namespace NAK.AvatarScaleMod.Integrations
_asmRootPage.MenuSubtitle = $"Everything Avatar Scaling! :: ({modUserCount}/{playerCount} players using ASM)";
}
#endregion
#endregion
#region Double-Click Reset Height
#region Double-Click Reset Height
private static DateTime lastTime = DateTime.Now;
private static DateTime lastTime = DateTime.Now;
private static void OnTabChange(string newTab, string previousTab)
{
private static void OnTabChange(string newTab, string previousTab)
{
if (newTab == _rootPageElementID)
{
UpdatePlayerCountDisplay();
@ -95,6 +95,5 @@ namespace NAK.AvatarScaleMod.Integrations
lastTime = DateTime.Now;
}
#endregion
}
#endregion
}

View file

@ -1,11 +1,11 @@
using BTKUILib.UIObjects;
namespace NAK.AvatarScaleMod.Integrations
namespace NAK.AvatarScaleMod.Integrations;
public static partial class BtkUiAddon
{
public static partial class BtkUiAddon
private static void Setup_AvatarScaleModCategory(Page page)
{
private static void Setup_AvatarScaleModCategory(Page page)
{
Category avScaleModCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_ASM_SettingsCategory);
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryScaleGestureEnabled);
@ -13,5 +13,4 @@ namespace NAK.AvatarScaleMod.Integrations
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistentHeight);
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistThroughRestart);
}
}
}

View file

@ -1,12 +1,12 @@
using BTKUILib.UIObjects;
using BTKUILib.UIObjects.Components;
namespace NAK.AvatarScaleMod.Integrations
namespace NAK.AvatarScaleMod.Integrations;
public static partial class BtkUiAddon
{
public static partial class BtkUiAddon
private static void Setup_AvatarScaleToolCategory(Page page)
{
private static void Setup_AvatarScaleToolCategory(Page page)
{
Category avScaleToolCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_AST_SettingsCategory);
AddMelonStringInput(ref avScaleToolCategory, ModSettings.EntryASTScaleParameter, "icon");
@ -19,5 +19,4 @@ namespace NAK.AvatarScaleMod.Integrations
ModSettings.EntryASTMaxHeight.ResetToDefault();
};
}
}
}

View file

@ -1,16 +1,15 @@
using BTKUILib.UIObjects;
namespace NAK.AvatarScaleMod.Integrations
namespace NAK.AvatarScaleMod.Integrations;
public static partial class BtkUiAddon
{
public static partial class BtkUiAddon
private static void Setup_DebugOptionsCategory(Page page)
{
private static void Setup_DebugOptionsCategory(Page page)
{
Category debugCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_DEBUG_SettingsCategory);
AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkInbound);
AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkOutbound);
AddMelonToggle(ref debugCategory, ModSettings.Debug_ComponentSearchTime);
}
}
}

View file

@ -4,14 +4,14 @@ using BTKUILib.UIObjects.Components;
using NAK.AvatarScaleMod.AvatarScaling;
using System.Collections.Generic; // Added for list support
namespace NAK.AvatarScaleMod.Integrations
{
public static partial class BtkUiAddon
{
private static readonly List<QMUIElement> USM_QmUiElements = new();
namespace NAK.AvatarScaleMod.Integrations;
private static void Setup_UniversalScalingSettings(Page page)
{
public static partial class BtkUiAddon
{
private static readonly List<QMUIElement> USM_QmUiElements = new();
private static void Setup_UniversalScalingSettings(Page page)
{
Category uniScalingCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_USM_SettingsCategory);
SliderFloat scaleSlider = AddMelonSlider(ref uniScalingCategory, ModSettings.EntryHiddenAvatarHeight, AvatarScaleManager.DefaultMinHeight, AvatarScaleManager.DefaultMaxHeight);
@ -52,24 +52,23 @@ namespace NAK.AvatarScaleMod.Integrations
ModSettings.EntryUseUniversalScaling.OnEntryValueChanged.Subscribe((_, newValue) => OnUniversalScalingChanged(newValue));
}
private static void OnUniversalScalingChanged(bool value)
{
private static void OnUniversalScalingChanged(bool value)
{
foreach (QMUIElement uiElement in USM_QmUiElements)
uiElement.Disabled = !value;
}
#region Slider Events
#region Slider Events
private static void OnAvatarHeightSliderChanged(float height)
{
private static void OnAvatarHeightSliderChanged(float height)
{
AvatarScaleManager.Instance.SetTargetHeight(height);
}
private static void OnAvatarHeightSliderReset()
{
private static void OnAvatarHeightSliderReset()
{
AvatarScaleManager.Instance.Setting_UniversalScaling = false;
}
#endregion
}
#endregion
}

View file

@ -3,15 +3,15 @@ using BTKUILib.UIObjects;
using BTKUILib.UIObjects.Components;
using NAK.AvatarScaleMod.AvatarScaling;
namespace NAK.AvatarScaleMod.Integrations
namespace NAK.AvatarScaleMod.Integrations;
public static partial class BtkUiAddon
{
public static partial class BtkUiAddon
{
private static Button _playerHasModElement;
private static string _selectedPlayer;
private static Button _playerHasModElement;
private static string _selectedPlayer;
private static void Setup_PlayerSelectPage()
{
private static void Setup_PlayerSelectPage()
{
QuickMenuAPI.OnPlayerSelected += OnPlayerSelected;
Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.ASM_SettingsCategory, ModSettings.ModName);
@ -22,27 +22,27 @@ namespace NAK.AvatarScaleMod.Integrations
button.OnPress += OnCopyPlayerHeight;
}
#region QM Events
#region QM Events
private static void OnPlayerSelected(string _, string id)
{
private static void OnPlayerSelected(string _, string id)
{
_selectedPlayer = id;
UpdatePlayerHasModIcon();
}
private static void OnCopyPlayerHeight()
{
private static void OnCopyPlayerHeight()
{
float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer);
if (networkHeight < 0) return;
AvatarScaleManager.Instance.SetTargetHeight(networkHeight);
}
#endregion
#endregion
#region Private Methods
#region Private Methods
private static void UpdatePlayerHasModIcon()
{
private static void UpdatePlayerHasModIcon()
{
if (_playerHasModElement == null)
return;
@ -60,6 +60,5 @@ namespace NAK.AvatarScaleMod.Integrations
}
}
#endregion
}
#endregion
}

View file

@ -0,0 +1,77 @@
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

@ -3,12 +3,12 @@ using System.IO;
using System.Reflection;
// https://github.com/SDraw/ml_mods_cvr/blob/master/ml_amt/Scripts.cs
namespace NAK.AvatarScaleMod
namespace NAK.AvatarScaleMod;
static class Scripts
{
static class Scripts
public static string GetEmbeddedScript(string p_name)
{
public static string GetEmbeddedScript(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
@ -23,5 +23,4 @@ namespace NAK.AvatarScaleMod
return l_result;
}
}
}

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more