diff --git a/.DepricatedMods/Nevermind/Main.cs b/.DepricatedMods/Nevermind/Main.cs deleted file mode 100644 index 31c7110..0000000 --- a/.DepricatedMods/Nevermind/Main.cs +++ /dev/null @@ -1,36 +0,0 @@ -using ABI_RC.Core.IO; -using MelonLoader; -using UnityEngine; - -namespace NAK.Nevermind; - -public class Nevermind : MelonMod -{ - public override void OnUpdate() - { - if (!Input.GetKeyDown(KeyCode.Home)) return; - - CancelWorldDownloadJoinOnComplete(); - CancelWorldLoadJoin(); - } - - void CancelWorldDownloadJoinOnComplete() - { - var downloadManager = CVRDownloadManager.Instance; - downloadManager.ActiveWorldDownload = false; - foreach (var download in downloadManager._downloadTasks) - download.Value.JoinOnComplete = false; - } - - void CancelWorldLoadJoin() - { - var objectLoader = CVRObjectLoader.Instance; - if (!objectLoader._isLoadingWorld) - { - objectLoader.j.Bytes = null; - objectLoader.j.ObjectId = null; - objectLoader.IsLoadingWorldToJoin = false; - objectLoader.CurrentWorldLoadingStage = -1; - } - } -} \ No newline at end of file diff --git a/.DepricatedMods/Nevermind/format.json b/.DepricatedMods/Nevermind/format.json deleted file mode 100644 index 2361cc0..0000000 --- a/.DepricatedMods/Nevermind/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 129, - "name": "Nevermind", - "modversion": "1.0.0", - "gameversion": "2022r170p1", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Prevents VRIK from using toe bones in VR & optionaly FBT.\n\nVRIK calculates weird center of mass when toes are mapped, so it is sometimes desired to unmap toes to prevent an avatars feet from resting far back.\n\nPlease see the README for relevant imagery detailing the problem.", - "searchtags": [ - "toes", - "vrik", - "ik", - "feet" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r3/Nevermind.dll", - "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/Nevermind/", - "changelog": "- Initial Release\n- No double patching. Bad. Stinky. Dont do it.", - "embedcolor": "#ffc700" -} \ No newline at end of file diff --git a/AvatarScale/AvatarScaling/AvatarScaleManager.cs b/AvatarScale/AvatarScaling/AvatarScaleManager.cs index 906c057..2f2e8ef 100644 --- a/AvatarScale/AvatarScaling/AvatarScaleManager.cs +++ b/AvatarScale/AvatarScaling/AvatarScaleManager.cs @@ -42,16 +42,48 @@ public class AvatarScaleManager : MonoBehaviour get => _settingUniversalScaling; set { - if (value != _settingUniversalScaling && value == false) - _localAvatarScaler.UseTargetHeight = false; + if (_settingUniversalScaling == value) + return; _settingUniversalScaling = value; - _localAvatarScaler.UseTargetHeight = true; + + if (_localAvatarScaler != null) + _localAvatarScaler.UseTargetHeight = value; } } - public bool Setting_AnimationClipScalingOverride; - public bool Setting_PersistentHeight; + private bool _settingAnimationClipScalingOverride; + public bool Setting_AnimationClipScalingOverride + { + get => _settingAnimationClipScalingOverride; + set + { + if (_settingAnimationClipScalingOverride == value) + return; + + _settingAnimationClipScalingOverride = value; + + if (_localAvatarScaler != null) + _localAvatarScaler.overrideAnimationHeight = value; + } + } + + private bool _settingPersistentHeight; + public bool Setting_PersistentHeight + { + get => _settingPersistentHeight; + set + { + if (_settingPersistentHeight == value) + return; + + _settingPersistentHeight = value; + + // if (_localAvatarScaler != null) + // _localAvatarScaler.persistHeight = value; + } + } + private float _lastTargetHeight = -1f; public float LastTargetHeight { @@ -128,11 +160,7 @@ public class AvatarScaleManager : MonoBehaviour // this is to ensure that the height is also set at correct time during frame, no matter when it is called private IEnumerator HeightUpdateCoroutine() { - while (enabled) - { - yield return _heightUpdateYield; - } - + while (enabled) yield return _heightUpdateYield; // ReSharper disable once IteratorNeverReturns } diff --git a/AvatarScale/AvatarScaling/Components/LocalScaler.cs b/AvatarScale/AvatarScaling/Components/LocalScaler.cs index 8360d47..95bbd57 100644 --- a/AvatarScale/AvatarScaling/Components/LocalScaler.cs +++ b/AvatarScale/AvatarScaling/Components/LocalScaler.cs @@ -13,6 +13,9 @@ public class LocalScaler : BaseScaler { _animatorManager = GetComponentInParent().animatorManager; _isAvatarInstantiated = false; + + // listen for events + } #endregion @@ -73,7 +76,6 @@ public class LocalScaler : BaseScaler _animatedScaleFactor = scaleDifference.y; _animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight; _animatedScale = localScale; - InvokeAnimatedHeightChanged(); if (overrideAnimationHeight || !_useTargetHeight) diff --git a/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs b/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs index e62c3a4..4f9cd55 100644 --- a/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs +++ b/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs @@ -28,11 +28,8 @@ public static class AvatarScaleEvents #endregion - #region Avatar Scaling Events - - /// /// Invoked when a remote avatar's height changes. /// diff --git a/BetterShadowClone/ModSettings.cs b/BetterShadowClone/ModSettings.cs index a2526f9..de687fa 100644 --- a/BetterShadowClone/ModSettings.cs +++ b/BetterShadowClone/ModSettings.cs @@ -31,6 +31,14 @@ public static class ModSettings internal static readonly MelonPreferences_Entry EntryDebugHeadHide = Category.CreateEntry("Debug Head Hide", false, description: "Should head be hidden for first render?"); + + internal static readonly MelonPreferences_Entry EntryDebugShowShadow = + Category.CreateEntry("Debug Show Shadow", false, + description: "Should the shadow clone be shown?"); + + internal static readonly MelonPreferences_Entry EntryDebugShowInFront = + Category.CreateEntry("Debug Show in Front", false, + description: "Should the shadow clone be shown in front?"); #endregion @@ -46,5 +54,7 @@ public static class ModSettings TransformHiderManager.s_DisallowFprExclusions = EntryDontRespectFPR.Value; TransformHiderManager.s_DebugHeadHide = EntryDebugHeadHide.Value; ShadowCloneManager.s_CopyMaterialsToShadow = EntryCopyMaterialToShadow.Value; + ShadowCloneManager.s_DebugShowShadow = EntryDebugShowShadow.Value; + ShadowCloneManager.s_DebugShowInFront = EntryDebugShowInFront.Value; } } \ No newline at end of file diff --git a/BetterShadowClone/Resources/bettershadowclone.assets b/BetterShadowClone/Resources/bettershadowclone.assets index a80c900..42c1734 100644 Binary files a/BetterShadowClone/Resources/bettershadowclone.assets and b/BetterShadowClone/Resources/bettershadowclone.assets differ diff --git a/BetterShadowClone/Resources/bettershadowclone.assets_OLD b/BetterShadowClone/Resources/bettershadowclone.assets_OLD new file mode 100644 index 0000000..a80c900 Binary files /dev/null and b/BetterShadowClone/Resources/bettershadowclone.assets_OLD differ diff --git a/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs b/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs index 7d121b2..e6cc161 100644 --- a/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs +++ b/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs @@ -8,4 +8,5 @@ public interface IShadowClone : IDisposable bool Process(); void RenderForShadow(); void RenderForUiCulling(); + void ResetMainMesh(); } \ No newline at end of file diff --git a/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs b/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs index f2670bd..d27883b 100644 --- a/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs +++ b/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs @@ -28,6 +28,8 @@ public struct MeshShadowClone : IShadowClone #region IShadowClone Methods + public void ResetMainMesh(){} + public bool IsValid => _mainMesh != null && _shadowMesh != null; public MeshShadowClone(MeshRenderer meshRenderer) @@ -88,7 +90,9 @@ public struct MeshShadowClone : IShadowClone public void RenderForShadow() { - _shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + _shadowMesh.shadowCastingMode = ShadowCloneManager.s_DebugShowShadow + ? ShadowCastingMode.On : ShadowCastingMode.ShadowsOnly; + _shadowMesh.forceRenderingOff = !_shouldCastShadows; // shadow casting needs clone to have original materials (uv discard) diff --git a/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs b/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs index 71e86a6..89d7494 100644 --- a/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs +++ b/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs @@ -8,6 +8,8 @@ public class SkinnedShadowClone : IShadowClone { private static readonly int s_SourceBufferId = Shader.PropertyToID("_sourceBuffer"); private static readonly int s_TargetBufferId = Shader.PropertyToID("_targetBuffer"); + private static readonly int s_HiddenVerticiesId = Shader.PropertyToID("_hiddenVertices"); + private static readonly int s_HiddenVertexPos = Shader.PropertyToID("_hiddenVertexPos"); private static readonly int s_SourceBufferLayoutId = Shader.PropertyToID("_sourceBufferLayout"); private static readonly int s_SourceRootMatrix = Shader.PropertyToID("_rootBoneMatrix"); @@ -26,6 +28,7 @@ public class SkinnedShadowClone : IShadowClone // clone copying private GraphicsBuffer _graphicsBuffer; private GraphicsBuffer _targetBuffer; + private ComputeBuffer _computeBuffer; private int _threadGroups; private int _bufferLayout; @@ -39,7 +42,7 @@ public class SkinnedShadowClone : IShadowClone // anything player can touch is suspect to death public bool IsValid => _mainMesh != null && _shadowMesh != null && _rootBone != null; - internal SkinnedShadowClone(SkinnedMeshRenderer renderer) + internal SkinnedShadowClone(SkinnedMeshRenderer renderer, FPRExclusion exclusion) { _mainMesh = renderer; @@ -52,11 +55,28 @@ public class SkinnedShadowClone : IShadowClone return; // no mesh! } + + FindExclusionVertList(_mainMesh, exclusion); + + if (exclusion.affectedVertexIndices.Count == 0) + { + Dispose(); + return; // no affected verts! + } + + _computeBuffer = new ComputeBuffer(_mainMesh.sharedMesh.vertexCount, sizeof(int)); + _computeBuffer.SetData(exclusion.affectedVertexIndices.ToArray()); + exclusion.affectedVertexIndices.Clear(); + + + _shouldCastShadows = _mainMesh.shadowCastingMode != ShadowCastingMode.Off; - _mainMesh.shadowCastingMode = ShadowCastingMode.Off; // visual mesh doesn't cast shadows + //_mainMesh.shadowCastingMode = ShadowCastingMode.On; // visual mesh doesn't cast shadows (_shadowMesh, _shadowMeshFilter) = ShadowCloneManager.InstantiateShadowClone(_mainMesh); - _shadowMesh.forceRenderingOff = true; + _shadowMesh.shadowCastingMode = ShadowCastingMode.Off; // shadow mesh doesn't cast shadows + _shadowMesh.forceRenderingOff = false; + _rootBone = _mainMesh.rootBone; _rootBone ??= _mainMesh.transform; // fallback to transform if no root bone @@ -133,6 +153,8 @@ public class SkinnedShadowClone : IShadowClone _graphicsBuffer = null; _targetBuffer?.Dispose(); _targetBuffer = null; + _computeBuffer?.Dispose(); + _computeBuffer = null; } #endregion @@ -151,7 +173,7 @@ public class SkinnedShadowClone : IShadowClone if (mesh.HasVertexAttribute(VertexAttribute.Tangent)) _bufferLayout += 4; _bufferLayout *= 4; // 4 bytes per float - const float xThreadGroups = 64f; + const float xThreadGroups = 32f; _threadGroups = Mathf.CeilToInt(mesh.vertexCount / xThreadGroups); _mainMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw; @@ -162,10 +184,66 @@ public class SkinnedShadowClone : IShadowClone //Debug.Log($"Initialized! BufferLayout: {_bufferLayout}, GraphicsBuffer: {_graphicsBuffer != null}, TargetBuffer: {_targetBuffer != null}"); } + public static void FindExclusionVertList(SkinnedMeshRenderer renderer, FPRExclusion exclusion) + { + var boneWeights = renderer.sharedMesh.boneWeights; + + HashSet weights = new(); + for (int i = 0; i < renderer.bones.Length; i++) + { + if (exclusion.affectedChildren.Contains(renderer.bones[i])) + weights.Add(i); + } + + for (int i = 0; i < boneWeights.Length; i++) + { + BoneWeight weight = boneWeights[i]; + + Transform bone = null; + const float minWeightThreshold = 0.2f; + if (weights.Contains(weight.boneIndex0) && weight.weight0 > minWeightThreshold) + bone = renderer.bones[weight.boneIndex0]; + else if (weights.Contains(weight.boneIndex1) && weight.weight1 > minWeightThreshold) + bone = renderer.bones[weight.boneIndex1]; + else if (weights.Contains(weight.boneIndex2) && weight.weight2 > minWeightThreshold) + bone = renderer.bones[weight.boneIndex2]; + else if (weights.Contains(weight.boneIndex3) && weight.weight3 > minWeightThreshold) + bone = renderer.bones[weight.boneIndex3]; + + exclusion.affectedVertexIndices.Add(bone != null ? i : -1); + } + } + + public void ResetMainMesh() + { + _mainMesh.shadowCastingMode = ShadowCastingMode.On; + _mainMesh.forceRenderingOff = false; + + _shadowMesh.transform.position = Vector3.positiveInfinity; // nan + } + private void ResetShadowClone() { - _shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; - _shadowMesh.forceRenderingOff = !_shouldCastShadows; + if (ShadowCloneManager.s_DebugShowShadow) + { + _mainMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + _mainMesh.forceRenderingOff = false; + + _shadowMesh.shadowCastingMode = ShadowCastingMode.On; + _shadowMesh.forceRenderingOff = false; + + _shadowMesh.transform.localPosition = Vector3.zero; + } + else + { + _mainMesh.shadowCastingMode = ShadowCastingMode.On; + _mainMesh.forceRenderingOff = false; + + _shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + _shadowMesh.forceRenderingOff = !_shouldCastShadows; + } + + //_shadowMesh.enabled = true; // shadow casting needs clone to have original materials (uv discard) // we also want to respect material swaps... but this is fucking slow :( @@ -173,12 +251,7 @@ public class SkinnedShadowClone : IShadowClone if (!ShadowCloneManager.s_CopyMaterialsToShadow) return; - if (_hasShadowMaterials) - { - _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials; - _hasShadowMaterials = false; - } - + _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials; UpdateCloneMaterialProperties(); } @@ -188,12 +261,10 @@ public class SkinnedShadowClone : IShadowClone _shadowMesh.forceRenderingOff = false; // UI culling needs clone to have write-to-depth shader - if (_hasShadowMaterials) return; _shadowMesh.sharedMaterials = _shadowMaterials; - _hasShadowMaterials = true; // Not needed- MaterialPropertyBlock applied to renderer in RenderForShadow - //UpdateCloneMaterialProperties(); + UpdateCloneMaterialProperties(); } private void RenderShadowClone() @@ -205,6 +276,10 @@ public class SkinnedShadowClone : IShadowClone ShadowCloneHelper.shader.SetMatrix(s_SourceRootMatrix, rootMatrix); ShadowCloneHelper.shader.SetBuffer(0, s_SourceBufferId, _graphicsBuffer); ShadowCloneHelper.shader.SetBuffer(0, s_TargetBufferId, _targetBuffer); + + ShadowCloneHelper.shader.SetBuffer(0, s_HiddenVerticiesId, _computeBuffer); + ShadowCloneHelper.shader.SetVector(s_HiddenVertexPos, Vector4.positiveInfinity); // temp + ShadowCloneHelper.shader.SetInt(s_SourceBufferLayoutId, _bufferLayout); ShadowCloneHelper.shader.Dispatch(0, _threadGroups, 1, 1); _graphicsBuffer.Release(); diff --git a/BetterShadowClone/ShadowClone/ShadowCloneManager.cs b/BetterShadowClone/ShadowClone/ShadowCloneManager.cs index a8f6706..4443efd 100644 --- a/BetterShadowClone/ShadowClone/ShadowCloneManager.cs +++ b/BetterShadowClone/ShadowClone/ShadowCloneManager.cs @@ -35,11 +35,14 @@ public class ShadowCloneManager : MonoBehaviour // Settings internal static bool s_CopyMaterialsToShadow = true; + internal static bool s_DebugShowShadow = false; + internal static bool s_DebugShowInFront = false; private static bool s_UseShadowToCullUi; private const string ShadowCullUiSettingName = "ExperimentalAvatarOverrenderUI"; // Implementation private bool _hasRenderedThisFrame; + public static readonly List s_Exclusions = new(); // Shadow Clones private readonly List s_ShadowClones = new(); @@ -64,15 +67,17 @@ public class ShadowCloneManager : MonoBehaviour UpdatePlayerCameras(); s_CopyMaterialsToShadow = ModSettings.EntryCopyMaterialToShadow.Value; + s_DebugShowShadow = ModSettings.EntryDebugShowShadow.Value; + s_DebugShowInFront = ModSettings.EntryDebugShowInFront.Value; s_UseShadowToCullUi = MetaPort.Instance.settings.GetSettingsBool(ShadowCullUiSettingName); MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged); } private void OnEnable() - => Camera.onPreCull += MyOnPreCull; + => Camera.onPreRender += MyOnPreCull; private void OnDisable() - => Camera.onPreCull -= MyOnPreCull; + => Camera.onPreRender -= MyOnPreCull; private void OnDestroy() { @@ -86,12 +91,25 @@ public class ShadowCloneManager : MonoBehaviour private void Update() { _hasRenderedThisFrame = false; + + for (int i = s_ShadowClones.Count - 1; i >= 0; i--) + { + IShadowClone clone = s_ShadowClones[i]; + if (clone is not { IsValid: true }) + { + clone?.Dispose(); + s_ShadowClones.RemoveAt(i); + continue; // invalid or dead + } + + clone.ResetMainMesh(); + } } private void MyOnPreCull(Camera cam) { - bool forceRenderForUiCull = s_UseShadowToCullUi && cam == s_UiCamera; - if (_hasRenderedThisFrame && !forceRenderForUiCull) + //bool forceRenderForUiCull = s_UseShadowToCullUi && cam == s_UiCamera; + if (cam != s_MainCamera) return; _hasRenderedThisFrame = true; @@ -109,17 +127,13 @@ public class ShadowCloneManager : MonoBehaviour } if (!clone.Process()) continue; // not ready yet or disabled - - if (forceRenderForUiCull) - clone.RenderForUiCulling(); // last cam to render - else - clone.RenderForShadow(); // first cam to render + + clone.RenderForShadow(); // first cam to render } _stopWatch.Stop(); - if (_debugShadowProcessingTime) Debug.Log($"ShadowCloneManager.MyOnPreCull({forceRenderForUiCull}) took {_stopWatch.ElapsedMilliseconds}ms"); } - + #endregion #region Game Events @@ -136,7 +150,12 @@ public class ShadowCloneManager : MonoBehaviour private void OnSettingsBoolChanged(string settingName, bool settingValue) { if (settingName == ShadowCullUiSettingName) + { s_UseShadowToCullUi = settingValue; + s_UiCamera.cullingMask = settingValue // make UI camera not see CVRLayers.PlayerClone + ? s_UiCamera.cullingMask | (1 << CVRLayers.PlayerClone) + : s_UiCamera.cullingMask & ~(1 << CVRLayers.PlayerClone); + } } private void OnVRModeSwitchCompleted(bool _, Camera __) @@ -152,6 +171,7 @@ public class ShadowCloneManager : MonoBehaviour { s_MainCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent(); s_UiCamera = s_MainCamera.transform.Find("_UICamera").GetComponent(); + //s_PortableCamera = PortableCamera.Instance.cameraComponent; } @@ -159,11 +179,11 @@ public class ShadowCloneManager : MonoBehaviour #region Static Helpers - internal static IShadowClone CreateShadowClone(Renderer renderer) + internal static IShadowClone CreateShadowClone(Renderer renderer, FPRExclusion exclusion) { return renderer switch { - SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedShadowClone(skinnedMeshRenderer), + SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedShadowClone(skinnedMeshRenderer, exclusion), MeshRenderer meshRenderer => new MeshShadowClone(meshRenderer), _ => null }; @@ -175,6 +195,14 @@ public class ShadowCloneManager : MonoBehaviour shadowClone.transform.SetParent(meshRenderer.transform, false); shadowClone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); shadowClone.transform.localScale = Vector3.one; + + if (s_DebugShowShadow && s_DebugShowInFront) + { + float scale = PlayerSetup.Instance.GetPlaySpaceScale(); + Transform playerTransform = PlayerSetup.Instance.transform; + shadowClone.transform.position += playerTransform.forward * scale * 1f; + shadowClone.transform.rotation = Quaternion.AngleAxis(180f, playerTransform.up) * shadowClone.transform.rotation; + } MeshRenderer newMesh = shadowClone.AddComponent(); MeshFilter newMeshFilter = shadowClone.AddComponent(); @@ -188,6 +216,9 @@ public class ShadowCloneManager : MonoBehaviour newMeshFilter.sharedMesh = meshRenderer.sharedMesh; newMesh.sharedMaterials = meshRenderer.sharedMaterials; + // copy probe anchor + newMesh.probeAnchor = meshRenderer.probeAnchor; + return (newMesh, newMeshFilter); } @@ -209,6 +240,9 @@ public class ShadowCloneManager : MonoBehaviour // copy mesh and materials newMeshFilter.sharedMesh = meshRenderer.GetComponent().sharedMesh; newMesh.sharedMaterials = meshRenderer.sharedMaterials; + + // copy probe anchor + newMesh.probeAnchor = meshRenderer.probeAnchor; return (newMesh, newMeshFilter); } diff --git a/BetterShadowClone/ShadowCloneHelper.cs b/BetterShadowClone/ShadowCloneHelper.cs index 29f6a7e..797ebb7 100644 --- a/BetterShadowClone/ShadowCloneHelper.cs +++ b/BetterShadowClone/ShadowCloneHelper.cs @@ -39,6 +39,8 @@ public static class ShadowCloneHelper return; } + ShadowCloneMod.Logger.Msg($"Found {renderers.Length} renderers. Processing..."); + // create shadow clones ProcessRenderers(renderers, avatar.transform, headBone); } @@ -48,6 +50,7 @@ public static class ShadowCloneHelper Stopwatch sw = Stopwatch.StartNew(); IReadOnlyDictionary exclusions = CollectTransformToExclusionMap(root, headBone); + var exclusion = headBone.gameObject.GetComponent(); // log current time ShadowCloneMod.Logger.Msg($"CollectTransformToExclusionMap in {sw.ElapsedMilliseconds}ms"); @@ -58,12 +61,12 @@ public static class ShadowCloneHelper if (ModSettings.EntryUseShadowClone.Value) { - IShadowClone clone = ShadowCloneManager.CreateShadowClone(renderer); + IShadowClone clone = ShadowCloneManager.CreateShadowClone(renderer, exclusion); if (clone != null) ShadowCloneManager.Instance.AddShadowClone(clone); } - ITransformHider hider = TransformHiderManager.CreateTransformHider(renderer, exclusions); - if (hider != null) TransformHiderManager.Instance.AddTransformHider(hider); + // ITransformHider hider = TransformHiderManager.CreateTransformHider(renderer, exclusions); + // if (hider != null) TransformHiderManager.Instance.AddTransformHider(hider); } sw.Stop(); @@ -143,9 +146,9 @@ public static class ShadowCloneHelper // shadow clone optimizations (always MeshRenderer) if (isShadowClone) { - renderer.receiveShadows = false; - renderer.lightProbeUsage = LightProbeUsage.Off; - renderer.reflectionProbeUsage = ReflectionProbeUsage.Off; + // renderer.receiveShadows = false; + // renderer.lightProbeUsage = LightProbeUsage.Off; + // renderer.reflectionProbeUsage = ReflectionProbeUsage.Off; return; } diff --git a/BetterShadowClone_LegacyHideAttempt.7z b/BetterShadowClone_LegacyHideAttempt.7z new file mode 100644 index 0000000..76c782a Binary files /dev/null and b/BetterShadowClone_LegacyHideAttempt.7z differ diff --git a/FuckCameraIndicator/FuckCameraIndicator.csproj b/FuckCameraIndicator/FuckCameraIndicator.csproj new file mode 100644 index 0000000..e94f9dc --- /dev/null +++ b/FuckCameraIndicator/FuckCameraIndicator.csproj @@ -0,0 +1,2 @@ + + diff --git a/FuckCameraIndicator/Main.cs b/FuckCameraIndicator/Main.cs new file mode 100644 index 0000000..a0c7957 --- /dev/null +++ b/FuckCameraIndicator/Main.cs @@ -0,0 +1,34 @@ +using MelonLoader; +using System.Reflection; +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.FuckCameraIndicator; + +public class FuckCameraIndicator : MelonMod +{ + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.Start), BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyLib.HarmonyMethod(typeof(FuckCameraIndicator).GetMethod(nameof(OnPuppetMasterStart_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static void OnPuppetMasterStart_Postfix(PuppetMaster __instance) + { + // thanks for not making it modular, fucking spaghetti + // and why leave it a skinned mesh... lazy fucking implementation + + GameObject indicator = __instance.cameraIndicator; + GameObject lens = __instance.cameraIndicatorLense; + + // Disable NamePlate child object + const string c_CanvasPath = "[NamePlate]/Canvas"; + GameObject canvas = indicator.transform.Find(c_CanvasPath).gameObject; + canvas.SetActive(false); + + // Disable lens renderer + lens.GetComponent().forceRenderingOff = true; + } +} \ No newline at end of file diff --git a/FuckCameraIndicator/Properties/AssemblyInfo.cs b/FuckCameraIndicator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b389d85 --- /dev/null +++ b/FuckCameraIndicator/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using MelonLoader; +using NAK.FuckCameraIndicator.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.FuckCameraIndicator))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.FuckCameraIndicator))] + +[assembly: MelonInfo( + typeof(NAK.FuckCameraIndicator.FuckCameraIndicator), + nameof(NAK.FuckCameraIndicator), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckCameraIndicator" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] + +namespace NAK.FuckCameraIndicator.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/FuckCameraIndicator/README.md b/FuckCameraIndicator/README.md new file mode 100644 index 0000000..af9d39c --- /dev/null +++ b/FuckCameraIndicator/README.md @@ -0,0 +1,16 @@ +# EzCurls + +A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures. + +The settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too. + +--- + +Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/FuckCameraIndicator/format.json b/FuckCameraIndicator/format.json new file mode 100644 index 0000000..6a8d72c --- /dev/null +++ b/FuckCameraIndicator/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "EzCurls", + "modversion": "1.0.0", + "gameversion": "2023r173", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures.\n\nThe settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too.", + "searchtags": [ + "curls", + "fingers", + "index", + "knuckles" + ], + "requirements": [ + "UIExpansionKit" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r24/EzCurls.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/EzCurls/", + "changelog": "- Initial CVRMG release", + "embedcolor": "7d7d7d" +} \ No newline at end of file diff --git a/MirrorClone/Main.cs b/MirrorClone/Main.cs new file mode 100644 index 0000000..87afe20 --- /dev/null +++ b/MirrorClone/Main.cs @@ -0,0 +1,80 @@ +using MelonLoader; +using System.Reflection; +using ABI_RC.Core.Player; +using ABI_RC.Core.Util; +using ABI_RC.Systems.IK; +using UnityEngine; + +namespace NAK.BetterShadowClone; + +public class MirrorCloneMod : MelonMod +{ + internal static MelonLogger.Instance Logger; + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ModSettings.Initialize(); + + try + { + InitializePatches(); + } + catch (Exception e) + { + Logger.Error(e); + } + } + + #region Harmony Patches + + private void InitializePatches() + { + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Awake), BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_Awake_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)), + prefix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_SetupAvatar_Prefix), BindingFlags.NonPublic | BindingFlags.Static)), + postfix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_SetupAvatar_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), + prefix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_ClearAvatar_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(IKSystem).GetMethod(nameof(IKSystem.OnPostSolverUpdateGeneral), BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnIKSystem_OnPostSolverUpdateGeneral_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(TransformHiderForMainCamera).GetMethod(nameof(TransformHiderForMainCamera.ProcessHierarchy)), + prefix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnTransformHiderForMainCamera_ProcessHierarchy_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static void OnPlayerSetup_Awake_Postfix() + => MirrorCloneManager.OnPlayerSetupAwake(); + + private static void OnPlayerSetup_SetupAvatar_Prefix(GameObject inAvatar) + => MirrorCloneManager.Instance.OnAvatarInitialized(inAvatar); + + private static void OnPlayerSetup_SetupAvatar_Postfix() + => MirrorCloneManager.Instance.OnAvatarConfigured(); + + private static void OnPlayerSetup_ClearAvatar_Prefix() + => MirrorCloneManager.Instance.OnAvatarDestroyed(); + + private static void OnIKSystem_OnPostSolverUpdateGeneral_Postfix() + => MirrorCloneManager.Instance.OnPostSolverUpdateGeneral(); + + private static void OnTransformHiderForMainCamera_ProcessHierarchy_Prefix(ref bool __runOriginal) + => __runOriginal = !ModSettings.EntryEnabled.Value; + + #endregion +} \ No newline at end of file diff --git a/MirrorClone/MirrorClone.csproj b/MirrorClone/MirrorClone.csproj new file mode 100644 index 0000000..e94f9dc --- /dev/null +++ b/MirrorClone/MirrorClone.csproj @@ -0,0 +1,2 @@ + + diff --git a/MirrorClone/MirrorClone/MirrorCloneManager.cs b/MirrorClone/MirrorClone/MirrorCloneManager.cs new file mode 100644 index 0000000..035c9c0 --- /dev/null +++ b/MirrorClone/MirrorClone/MirrorCloneManager.cs @@ -0,0 +1,247 @@ +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.IK; +using ABI.CCK.Components; +using UnityEngine; +using UnityEngine.Rendering; + +namespace NAK.BetterShadowClone; + +public class MirrorCloneManager : MonoBehaviour +{ + #region Static Instance + + public static MirrorCloneManager Instance { get; private set; } + + #endregion + + private bool _isAvatarConfigured; + + private GameObject _avatar; + private GameObject _mirrorClone; + private GameObject _initializationTarget; + + private CVRAnimatorManager _animatorManager; + private Animator _mirrorAnimator; + + #region Unity Events + + private void Awake() + { + if (Instance != null + && Instance != this) + { + DestroyImmediate(this); + return; + } + + Instance = this; + + MirrorCloneMod.Logger.Msg("Mirror Clone Manager initialized."); + + _animatorManager = PlayerSetup.Instance.animatorManager; + + // Create initialization target (so no components are initialized before we're ready) + _initializationTarget = new GameObject(nameof(MirrorCloneManager) + " Initialization Target"); + _initializationTarget.transform.SetParent(transform); + _initializationTarget.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + _initializationTarget.transform.localScale = Vector3.one; + _initializationTarget.SetActive(false); + } + + private void OnDestroy() + { + if (Instance == this) + Instance = null; + } + + #endregion + + #region Game Events + + public static void OnPlayerSetupAwake() + { + if (Instance != null) + return; + + GameObject manager = new (nameof(MirrorCloneManager), typeof(MirrorCloneManager)); + DontDestroyOnLoad(manager); + } + + public void OnAvatarInitialized(GameObject avatar) + { + if (!ModSettings.EntryEnabled.Value) + return; + + if (avatar == null + || _isAvatarConfigured) + return; + + _isAvatarConfigured = true; + + _avatar = avatar; + _mirrorClone = InstantiateMirrorCopy(_avatar); + } + + public void OnAvatarConfigured() + { + if (!_isAvatarConfigured) + return; + + Animator baseAnimator = _avatar.GetComponent(); + + if (!_mirrorClone.TryGetComponent(out _mirrorAnimator)) + _mirrorAnimator = gameObject.AddComponent(); + _mirrorAnimator.runtimeAnimatorController = baseAnimator.runtimeAnimatorController; + + _animatorManager._copyAnimator = _mirrorAnimator; // thank you for existing + + var cameras = PlayerSetup.Instance.GetComponentsInChildren(true); + foreach (var camera in cameras) + { + // hide PlayerClone layer from all cameras + camera.cullingMask &= ~(1 << CVRLayers.PlayerClone); + } + + var mirrors = Resources.FindObjectsOfTypeAll(); + foreach (CVRMirror mirror in mirrors) + { + // hide PlayerLocal layer from all mirrors + mirror.m_ReflectLayers &= ~(1 << CVRLayers.PlayerLocal); + } + + // scale avatar head bone to 0 0 0 + Transform headBone = baseAnimator.GetBoneTransform(HumanBodyBones.Head); + headBone.localScale = Vector3.zero; + + CleanupAvatar(); + CleanupMirrorClone(); + SetupHumanPoseHandler(); + + _initializationTarget.SetActive(true); + } + + public void OnAvatarDestroyed() + { + if (!_isAvatarConfigured) + return; + + _avatar = null; + _mirrorAnimator = null; + if (_mirrorClone != null) + Destroy(_mirrorClone); + + _initializationTarget.SetActive(false); + + _isAvatarConfigured = false; + } + + public void OnPostSolverUpdateGeneral() + { + if (!_isAvatarConfigured) + return; + + StealTransforms(); + } + + #endregion + + #region Private Methods + + private GameObject InstantiateMirrorCopy(GameObject original) + { + GameObject clone = Instantiate(original, _initializationTarget.transform); + clone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + clone.name = original.name + " (Mirror Clone)"; + clone.SetLayerRecursive(CVRLayers.PlayerClone); + return clone; + } + + private void CleanupAvatar() + { + // set local avatar mesh to shadow off + var avatarMeshes = _avatar.GetComponentsInChildren(true); + foreach (SkinnedMeshRenderer avatarMesh in avatarMeshes) + { + avatarMesh.shadowCastingMode = ShadowCastingMode.Off; + avatarMesh.forceMatrixRecalculationPerRender = false; + } + } + + private void CleanupMirrorClone() + { + // destroy unneeded components + // only keep Animator + + var components = _mirrorClone.GetComponentsInChildren(true); + foreach (Component component in components) + { + if (component == null) + continue; + + // skip basic unity components + if (component is Animator + or Transform + or SkinnedMeshRenderer + or MeshRenderer + or MeshFilter) + continue; + + // skip basic CVR components + if (component is CVRAvatar or CVRAssetInfo) + { + (component as MonoBehaviour).enabled = false; + continue; + } + + Destroy(component); + } + } + + #endregion + + #region Job System + + private HumanPoseHandler _humanPoseHandler; + private Transform _hipTransform; + + private void SetupHumanPoseHandler() + { + _hipTransform = _mirrorAnimator.GetBoneTransform(HumanBodyBones.Hips); + + _humanPoseHandler?.Dispose(); + _humanPoseHandler = new HumanPoseHandler(_mirrorAnimator.avatar, _mirrorAnimator.transform); + } + + private void StealTransforms() + { + // copy transforms from avatar to mirror clone + // var avatarTransforms = _avatar.GetComponentsInChildren(true); + // var mirrorCloneTransforms = _mirrorClone.GetComponentsInChildren(true); + // for (int i = 0; i < avatarTransforms.Length; i++) + // { + // Transform avatarTransform = avatarTransforms[i]; + // Transform mirrorCloneTransform = mirrorCloneTransforms[i]; + // + // mirrorCloneTransform.SetLocalPositionAndRotation( + // avatarTransform.localPosition, + // avatarTransform.localRotation); + // } + + if (!IKSystem.Instance.IsAvatarCalibrated()) + return; + + IKSystem.Instance._humanPoseHandler.GetHumanPose(ref IKSystem.Instance._humanPose); + _humanPoseHandler.SetHumanPose(ref IKSystem.Instance._humanPose); + + if (!MetaPort.Instance.isUsingVr) + _mirrorAnimator.transform.SetPositionAndRotation(PlayerSetup.Instance.GetPlayerPosition(), PlayerSetup.Instance.GetPlayerRotation()); + else + _mirrorAnimator.transform.SetPositionAndRotation(_avatar.transform.position, _avatar.transform.rotation); + + _hipTransform.SetPositionAndRotation(IKSystem.Instance._hipTransform.position, IKSystem.Instance._hipTransform.rotation); + } + + #endregion +} \ No newline at end of file diff --git a/MirrorClone/ModSettings.cs b/MirrorClone/ModSettings.cs new file mode 100644 index 0000000..05c753a --- /dev/null +++ b/MirrorClone/ModSettings.cs @@ -0,0 +1,31 @@ +using MelonLoader; +using UnityEngine; + +namespace NAK.BetterShadowClone; + +public static class ModSettings +{ + #region Melon Prefs + + private const string SettingsCategory = nameof(MirrorCloneMod); + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(SettingsCategory); + + internal static readonly MelonPreferences_Entry EntryEnabled = + Category.CreateEntry("Enabled", true, + description: "Enable Mirror Clone."); + + #endregion + + internal static void Initialize() + { + foreach (MelonPreferences_Entry setting in Category.Entries) + setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); + } + + internal static void OnSettingsChanged(object oldValue = null, object newValue = null) + { + + } +} \ No newline at end of file diff --git a/MirrorClone/Properties/AssemblyInfo.cs b/MirrorClone/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..98c8620 --- /dev/null +++ b/MirrorClone/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using MelonLoader; +using NAK.BetterShadowClone.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.BetterShadowClone))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.BetterShadowClone))] + +[assembly: MelonInfo( + typeof(NAK.BetterShadowClone.MirrorCloneMod), + nameof(NAK.BetterShadowClone), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MirrorCloneMod" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] + +namespace NAK.BetterShadowClone.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/MirrorClone/README.md b/MirrorClone/README.md new file mode 100644 index 0000000..af9d39c --- /dev/null +++ b/MirrorClone/README.md @@ -0,0 +1,16 @@ +# EzCurls + +A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures. + +The settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too. + +--- + +Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/MirrorClone/format.json b/MirrorClone/format.json new file mode 100644 index 0000000..6a8d72c --- /dev/null +++ b/MirrorClone/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "EzCurls", + "modversion": "1.0.0", + "gameversion": "2023r173", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures.\n\nThe settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too.", + "searchtags": [ + "curls", + "fingers", + "index", + "knuckles" + ], + "requirements": [ + "UIExpansionKit" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r24/EzCurls.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/EzCurls/", + "changelog": "- Initial CVRMG release", + "embedcolor": "7d7d7d" +} \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 754d011..46c920f 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -35,6 +35,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MirrorClone", "MirrorClone\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterShadowClone", "BetterShadowClone\BetterShadowClone.csproj", "{D0C40987-AF16-490A-9304-F99D5A5A774C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckCameraIndicator", "FuckCameraIndicator\FuckCameraIndicator.csproj", "{0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouAreAClone", "YouAreAClone\YouAreAClone.csproj", "{7778B600-111E-4C8B-AE2B-E9750CECE02A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nevermind", "Nevermind\Nevermind.csproj", "{AC4857DD-F6D9-436D-A3EE-D148A518E642}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +111,18 @@ Global {D0C40987-AF16-490A-9304-F99D5A5A774C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0C40987-AF16-490A-9304-F99D5A5A774C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0C40987-AF16-490A-9304-F99D5A5A774C}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Release|Any CPU.Build.0 = Release|Any CPU + {7778B600-111E-4C8B-AE2B-E9750CECE02A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7778B600-111E-4C8B-AE2B-E9750CECE02A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7778B600-111E-4C8B-AE2B-E9750CECE02A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7778B600-111E-4C8B-AE2B-E9750CECE02A}.Release|Any CPU.Build.0 = Release|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Nevermind/Main.cs b/Nevermind/Main.cs new file mode 100644 index 0000000..c631850 --- /dev/null +++ b/Nevermind/Main.cs @@ -0,0 +1,53 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using MelonLoader; +using UnityEngine; + +namespace NAK.Nevermind; + +public class Nevermind : MelonMod +{ + #region Mod Settings + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(Nevermind)); + + private static readonly MelonPreferences_Entry Setting_NevermindKey = + Category.CreateEntry("Keybind", KeyCode.Home, description: "Key to cancel world join."); + + #endregion + + #region Melon Events + + public override void OnUpdate() + { + if (!Input.GetKeyDown(Setting_NevermindKey.Value)) + return; + + if (CVRObjectLoader.Instance == null + || CVRDownloadManager.Instance == null) + return; // game is not ready + + if (!CVRObjectLoader.Instance.IsLoadingWorldToJoin()) + return; // no world to cancel + + if (CVRObjectLoader.Instance.WorldBundleRequest != null) + return; // too late to cancel, world is being loaded + + // Cancel world join if still downloading + CVRDownloadManager.Instance.ActiveWorldDownload = false; + foreach (var download in CVRDownloadManager.Instance._downloadTasks) + download.Value.JoinOnComplete = false; + + // Cancel world join if still loading + CVRObjectLoader.Instance.ActiveWorldDownload = null; + CVRObjectLoader.Instance._isLoadingWorldToJoin = false; + CVRObjectLoader.Instance.worldLoadingState = CVRObjectLoader.WorldLoadingState.None; + + // Notify user of successful cancellation + LoggerInstance.Msg("World Join Cancelled!"); + ViewManager.Instance.NotifyUser("(Local) Client", "World Join Cancelled", 2f); + } + + #endregion +} \ No newline at end of file diff --git a/.DepricatedMods/Nevermind/Nevermind.csproj b/Nevermind/Nevermind.csproj similarity index 100% rename from .DepricatedMods/Nevermind/Nevermind.csproj rename to Nevermind/Nevermind.csproj diff --git a/.DepricatedMods/Nevermind/Properties/AssemblyInfo.cs b/Nevermind/Properties/AssemblyInfo.cs similarity index 90% rename from .DepricatedMods/Nevermind/Properties/AssemblyInfo.cs rename to Nevermind/Properties/AssemblyInfo.cs index 8e54d13..ed2c23a 100644 --- a/.DepricatedMods/Nevermind/Properties/AssemblyInfo.cs +++ b/Nevermind/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using Nevermind.Properties; +using NAK.Nevermind.Properties; using MelonLoader; using System.Reflection; @@ -20,10 +20,9 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonOptionalDependencies("BTKUILib")] [assembly: HarmonyDontPatchAll] -namespace Nevermind.Properties; +namespace NAK.Nevermind.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/Nevermind/format.json b/Nevermind/format.json new file mode 100644 index 0000000..9dcbab6 --- /dev/null +++ b/Nevermind/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "Nevermind", + "modversion": "1.0.0", + "gameversion": "2024r174", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Simple mod that allows you to cancel joining a world in Desktop.", + "searchtags": [ + "never", + "mind", + "download", + "cancel" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r25/Nevermind.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/Nevermind/", + "changelog": "- Initial Release", + "embedcolor": "#b589ec" +} \ No newline at end of file diff --git a/PhysicsGunMod/Components/ObjectSyncBridge.cs b/PhysicsGunMod/Components/ObjectSyncBridge.cs index ea3e1c7..b97eff9 100644 --- a/PhysicsGunMod/Components/ObjectSyncBridge.cs +++ b/PhysicsGunMod/Components/ObjectSyncBridge.cs @@ -23,32 +23,33 @@ public class ObjectSyncBridge : MonoBehaviour _physicsGun.OnPreGrabbedObject = o => { bool canTakeOwnership = false; - + + // CVRObjectSync objectSync = o.GetComponentInParent(); - if (objectSync != null - && (objectSync.SyncType == 0 // check if physics synced or synced by us - || objectSync.SyncedByMe)) - canTakeOwnership = true; - + // if (objectSync != null + // && (objectSync.SyncType == 0 // check if physics synced or synced by us + // || objectSync.SyncedByMe)) + // canTakeOwnership = true; + // CVRSpawnable spawnable = o.GetComponentInParent(); - if (spawnable != null) - { - CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == spawnable.instanceId); - if (propData != null - && (propData.syncType == 0 // check if physics synced or synced by us - || propData.syncedBy == MetaPort.Instance.ownerId)) - canTakeOwnership = true; - } - + // if (spawnable != null) + // { + // CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == spawnable.instanceId); + // if (propData != null + // && (propData.syncType == 0 // check if physics synced or synced by us + // || propData.syncedBy == MetaPort.Instance.ownerId)) + // canTakeOwnership = true; + // } + // CVRPickupObject pickup = o.GetComponentInParent(); - if (pickup != null - && (pickup.grabbedBy == MetaPort.Instance.ownerId // check if already grabbed by us - || pickup.grabbedBy == "" || !pickup.disallowTheft)) // check if not grabbed or allows theft - canTakeOwnership = true; - - if (!canTakeOwnership // if we can't take ownership, don't grab, unless there is no syncing at all (local object) - && (objectSync || spawnable || pickup )) - return false; + // if (pickup != null + // && (pickup.grabbedBy == MetaPort.Instance.ownerId // check if already grabbed by us + // || pickup.grabbedBy == "" || !pickup.disallowTheft)) // check if not grabbed or allows theft + // canTakeOwnership = true; + // + // if (!canTakeOwnership // if we can't take ownership, don't grab, unless there is no syncing at all (local object) + // && (objectSync || spawnable || pickup )) + // return false; if (pickup) { diff --git a/PropUndoButton/format.json b/PropUndoButton/format.json index e4af225..9cafa87 100644 --- a/PropUndoButton/format.json +++ b/PropUndoButton/format.json @@ -1,8 +1,8 @@ { "_id": 147, "name": "PropUndoButton", - "modversion": "1.0.1", - "gameversion": "2022r170p1", + "modversion": "1.0.2", + "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r3/PropUndoButton.dll", + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r25/PropUndoButton.dll", "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropUndoButton/", - "changelog": "- Initial Release\n- Added redo button.\n- Mitigated issue of props getting stuck locally if deleting them before they fully spawn.\n- Lowered SFX volume to match existing UI sounds.", + "changelog": "- Recompiled for 2024r175", "embedcolor": "#00FFFF" } \ No newline at end of file diff --git a/ThirdPerson/CameraLogic.cs b/ThirdPerson/CameraLogic.cs index 27d4dcf..cad39b2 100644 --- a/ThirdPerson/CameraLogic.cs +++ b/ThirdPerson/CameraLogic.cs @@ -70,7 +70,7 @@ internal static class CameraLogic return; ThirdPerson.Logger.Msg("Copying active camera settings & components."); - CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam); + CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam, true); if (!CheckIsRestricted()) return; diff --git a/ThirdPerson/Integrations/BSCAddon.cs b/ThirdPerson/Integrations/BSCAddon.cs deleted file mode 100644 index 65306af..0000000 --- a/ThirdPerson/Integrations/BSCAddon.cs +++ /dev/null @@ -1,11 +0,0 @@ -using NAK.BetterShadowClone; - -namespace NAK.ThirdPerson.Integrations; - -public static class BSCAddon -{ - public static void Initialize() - { - ShadowCloneMod.wantsToHideHead += CameraLogic.ShouldNotHideHead_ThirdPerson; - } -} \ No newline at end of file diff --git a/ThirdPerson/Properties/AssemblyInfo.cs b/ThirdPerson/Properties/AssemblyInfo.cs index 4fa680e..ad57b1b 100644 --- a/ThirdPerson/Properties/AssemblyInfo.cs +++ b/ThirdPerson/Properties/AssemblyInfo.cs @@ -1,7 +1,6 @@ using MelonLoader; using NAK.ThirdPerson.Properties; using System.Reflection; -using CurvedUI; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] @@ -24,11 +23,10 @@ using CurvedUI; [assembly: MelonColor(255, 246, 25, 97)] [assembly: MelonAuthorColor(255, 158, 21, 32)] [assembly: HarmonyDontPatchAll] -[assembly: MelonOptionalDependencies("BetterShadowClone")] namespace NAK.ThirdPerson.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.7"; + public const string Version = "1.0.8"; public const string Author = "Davi & NotAKidoS"; } \ No newline at end of file diff --git a/ThirdPerson/ThirdPerson.cs b/ThirdPerson/ThirdPerson.cs index 0017c44..21691c4 100644 --- a/ThirdPerson/ThirdPerson.cs +++ b/ThirdPerson/ThirdPerson.cs @@ -14,17 +14,6 @@ public class ThirdPerson : MelonMod Patches.Apply(HarmonyInstance); MelonCoroutines.Start(SetupCamera()); - - InitializeIntegration("BetterShadowClone", Integrations.BSCAddon.Initialize); - } - - private static void InitializeIntegration(string modName, Action integrationAction) - { - if (RegisteredMelons.All(it => it.Info.Name != modName)) - return; - - Logger.Msg($"Initializing {modName} integration."); - integrationAction.Invoke(); } public override void OnUpdate() diff --git a/ThirdPerson/ThirdPerson.csproj b/ThirdPerson/ThirdPerson.csproj index 9cb91de..13a6b34 100644 --- a/ThirdPerson/ThirdPerson.csproj +++ b/ThirdPerson/ThirdPerson.csproj @@ -1,6 +1,2 @@  - - - - - + diff --git a/ThirdPerson/format.json b/ThirdPerson/format.json index 054ff45..74f7a12 100644 --- a/ThirdPerson/format.json +++ b/ThirdPerson/format.json @@ -2,8 +2,8 @@ { "_id": 16, "name": "ThirdPerson", - "modversion": "1.0.7", - "gameversion": "2023r173", + "modversion": "1.0.8", + "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", "author": "Davi & NotAKidoS", @@ -14,9 +14,9 @@ "third person" ], "requirements": [], - "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r23/ThirdPerson.dll", + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r25/ThirdPerson.dll", "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ThirdPerson", - "changelog": "- Added restriction to worlds with Zoom disabled.", + "changelog": "- Recompiled to work with 2024r175", "embedcolor": "#F61961" } ] \ No newline at end of file diff --git a/YouAreAClone/Main.cs b/YouAreAClone/Main.cs new file mode 100644 index 0000000..217a888 --- /dev/null +++ b/YouAreAClone/Main.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using MelonLoader; +using System.Reflection; +using ABI_RC.Core.Player; +using ABI_RC.Core.Util; +using ABI_RC.Core.Util.AssetFiltering; +using ABI_RC.Systems.Camera; +using UnityEngine; + +namespace NAK.YouAreAClone; + +public class YouAreAClone : MelonMod +{ + internal static MelonLogger.Instance Logger; + + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + //VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(_ => FindCameras()); + + SharedFilter._avatarWhitelist.Add(typeof(FPRExclusion)); + SharedFilter._localComponentWhitelist.Add(typeof(FPRExclusion)); + + try + { + LoadAssetBundle(); + InitializePatches(); + } + catch (Exception e) + { + Logger.Error(e); + } + } + + #region Asset Bundle Loading + + private const string YouAreACloneAssets = "YouAreAClone.assets"; + + private const string BoneHiderComputePath = "Assets/Koneko/ComputeShaders/BoneHider.compute"; + //private const string MeshCopyComputePath = "Assets/Koneko/ComputeShaders/MeshCopy.compute"; + + private const string ShadowCloneComputePath = "Assets/NotAKid/Shaders/ShadowClone.compute"; + private const string ShadowCloneShaderPath = "Assets/NotAKid/Shaders/ShadowClone.shader"; + private const string DummyCloneShaderPath = "Assets/NotAKid/Shaders/DummyClone.shader"; + + private void LoadAssetBundle() + { + Logger.Msg($"Loading required asset bundle..."); + using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(YouAreACloneAssets); + using MemoryStream memoryStream = new(); + if (resourceStream == null) { + Logger.Error($"Failed to load {YouAreACloneAssets}!"); + return; + } + + resourceStream.CopyTo(memoryStream); + AssetBundle assetBundle = AssetBundle.LoadFromMemory(memoryStream.ToArray()); + if (assetBundle == null) { + Logger.Error($"Failed to load {YouAreACloneAssets}! Asset bundle is null!"); + return; + } + + // load shaders + ComputeShader shader = assetBundle.LoadAsset(BoneHiderComputePath); + shader.hideFlags |= HideFlags.DontUnloadUnusedAsset; + TransformHiderManager.shader = shader; + Logger.Msg($"Loaded {BoneHiderComputePath}!"); + + // load shadow clone shader + ComputeShader shadowCloneCompute = assetBundle.LoadAsset(ShadowCloneComputePath); + shadowCloneCompute.hideFlags |= HideFlags.DontUnloadUnusedAsset; + ShadowCloneHelper.shader = shadowCloneCompute; + Logger.Msg($"Loaded {ShadowCloneComputePath}!"); + + // load shadow clone material + Shader shadowCloneShader = assetBundle.LoadAsset(ShadowCloneShaderPath); + shadowCloneShader.hideFlags |= HideFlags.DontUnloadUnusedAsset; + ShadowCloneHelper.shadowMaterial = new Material(shadowCloneShader); + Logger.Msg($"Loaded {ShadowCloneShaderPath}!"); + + Logger.Msg("Asset bundle successfully loaded!"); + } + + #endregion + + #region Harmony Patches + + private void InitializePatches() + { + HarmonyInstance.Patch( + typeof(TransformHiderForMainCamera).GetMethod(nameof(TransformHiderForMainCamera.ProcessHierarchy)), + prefix: new HarmonyLib.HarmonyMethod(typeof(YouAreAClone).GetMethod(nameof(OnTransformHiderForMainCamera_ProcessHierarchy_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), + prefix: new HarmonyLib.HarmonyMethod(typeof(YouAreAClone).GetMethod(nameof(OnPlayerSetup_ClearAvatar_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static void OnPlayerSetup_ClearAvatar_Prefix() + { + TransformHiderManager.Instance.OnAvatarCleared(); + ShadowCloneManager.Instance.OnAvatarCleared(); + } + + private static void OnTransformHiderForMainCamera_ProcessHierarchy_Prefix(ref bool __runOriginal) + { + if (!__runOriginal || (__runOriginal = !ModSettings.EntryEnabled.Value)) + return; // if something else disabled, or we are disabled, don't run + + ShadowCloneHelper.SetupAvatar(PlayerSetup.Instance._avatar); + } + + #endregion +} \ No newline at end of file diff --git a/YouAreAClone/ModSettings.cs b/YouAreAClone/ModSettings.cs new file mode 100644 index 0000000..de687fa --- /dev/null +++ b/YouAreAClone/ModSettings.cs @@ -0,0 +1,60 @@ +using MelonLoader; +using UnityEngine; + +namespace NAK.BetterShadowClone; + +public static class ModSettings +{ + #region Melon Prefs + + private const string SettingsCategory = nameof(ShadowCloneMod); + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(SettingsCategory); + + internal static readonly MelonPreferences_Entry EntryEnabled = + Category.CreateEntry("Enabled", true, + description: "Enable Mirror Clone."); + + internal static readonly MelonPreferences_Entry EntryUseShadowClone = + Category.CreateEntry("Use Shadow Clone", true, + description: "Should you have shadow clones?"); + + internal static readonly MelonPreferences_Entry EntryCopyMaterialToShadow = + Category.CreateEntry("Copy Material to Shadow", true, + description: "Should the shadow clone copy the material from the original mesh? Note: This can have a slight performance hit."); + + internal static readonly MelonPreferences_Entry EntryDontRespectFPR = + Category.CreateEntry("Dont Respect FPR", false, + description: "Should the transform hider not respect FPR?"); + + internal static readonly MelonPreferences_Entry EntryDebugHeadHide = + Category.CreateEntry("Debug Head Hide", false, + description: "Should head be hidden for first render?"); + + internal static readonly MelonPreferences_Entry EntryDebugShowShadow = + Category.CreateEntry("Debug Show Shadow", false, + description: "Should the shadow clone be shown?"); + + internal static readonly MelonPreferences_Entry EntryDebugShowInFront = + Category.CreateEntry("Debug Show in Front", false, + description: "Should the shadow clone be shown in front?"); + + + #endregion + + internal static void Initialize() + { + foreach (MelonPreferences_Entry setting in Category.Entries) + setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); + } + + private static void OnSettingsChanged(object oldValue = null, object newValue = null) + { + TransformHiderManager.s_DisallowFprExclusions = EntryDontRespectFPR.Value; + TransformHiderManager.s_DebugHeadHide = EntryDebugHeadHide.Value; + ShadowCloneManager.s_CopyMaterialsToShadow = EntryCopyMaterialToShadow.Value; + ShadowCloneManager.s_DebugShowShadow = EntryDebugShowShadow.Value; + ShadowCloneManager.s_DebugShowInFront = EntryDebugShowInFront.Value; + } +} \ No newline at end of file diff --git a/YouAreAClone/Properties/AssemblyInfo.cs b/YouAreAClone/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f31ea23 --- /dev/null +++ b/YouAreAClone/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using MelonLoader; +using NAK.BetterShadowClone.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.BetterShadowClone))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.BetterShadowClone))] + +[assembly: MelonInfo( + typeof(NAK.BetterShadowClone.ShadowCloneMod), + nameof(NAK.BetterShadowClone), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ShadowCloneMod" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] + +namespace NAK.BetterShadowClone.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS & Exterrata"; +} \ No newline at end of file diff --git a/YouAreAClone/YouAreAClone.csproj b/YouAreAClone/YouAreAClone.csproj new file mode 100644 index 0000000..e94f9dc --- /dev/null +++ b/YouAreAClone/YouAreAClone.csproj @@ -0,0 +1,2 @@ + +