thirdperson, propundobutton, mirror clone test, you are a clone test, bettershadowclone test, nevermind, anotherlocaltestmod, and some changes to avatarscaling ???

This commit is contained in:
NotAKidoS 2024-04-10 17:25:21 -05:00
parent df45fb50d9
commit 9944ad7611
43 changed files with 1076 additions and 173 deletions

View file

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

View file

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

View file

@ -42,16 +42,48 @@ public class AvatarScaleManager : MonoBehaviour
get => _settingUniversalScaling; get => _settingUniversalScaling;
set set
{ {
if (value != _settingUniversalScaling && value == false) if (_settingUniversalScaling == value)
_localAvatarScaler.UseTargetHeight = false; return;
_settingUniversalScaling = value; _settingUniversalScaling = value;
_localAvatarScaler.UseTargetHeight = true;
if (_localAvatarScaler != null)
_localAvatarScaler.UseTargetHeight = value;
} }
} }
public bool Setting_AnimationClipScalingOverride; private bool _settingAnimationClipScalingOverride;
public bool Setting_PersistentHeight; 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; private float _lastTargetHeight = -1f;
public float LastTargetHeight 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 // this is to ensure that the height is also set at correct time during frame, no matter when it is called
private IEnumerator HeightUpdateCoroutine() private IEnumerator HeightUpdateCoroutine()
{ {
while (enabled) while (enabled) yield return _heightUpdateYield;
{
yield return _heightUpdateYield;
}
// ReSharper disable once IteratorNeverReturns // ReSharper disable once IteratorNeverReturns
} }

View file

@ -13,6 +13,9 @@ public class LocalScaler : BaseScaler
{ {
_animatorManager = GetComponentInParent<PlayerSetup>().animatorManager; _animatorManager = GetComponentInParent<PlayerSetup>().animatorManager;
_isAvatarInstantiated = false; _isAvatarInstantiated = false;
// listen for events
} }
#endregion #endregion
@ -73,7 +76,6 @@ public class LocalScaler : BaseScaler
_animatedScaleFactor = scaleDifference.y; _animatedScaleFactor = scaleDifference.y;
_animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight; _animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight;
_animatedScale = localScale; _animatedScale = localScale;
InvokeAnimatedHeightChanged();
if (overrideAnimationHeight if (overrideAnimationHeight
|| !_useTargetHeight) || !_useTargetHeight)

View file

@ -28,11 +28,8 @@ public static class AvatarScaleEvents
#endregion #endregion
#region Avatar Scaling Events #region Avatar Scaling Events
/// <summary> /// <summary>
/// Invoked when a remote avatar's height changes. /// Invoked when a remote avatar's height changes.
/// </summary> /// </summary>

View file

@ -31,6 +31,14 @@ public static class ModSettings
internal static readonly MelonPreferences_Entry<bool> EntryDebugHeadHide = internal static readonly MelonPreferences_Entry<bool> EntryDebugHeadHide =
Category.CreateEntry("Debug Head Hide", false, Category.CreateEntry("Debug Head Hide", false,
description: "Should head be hidden for first render?"); description: "Should head be hidden for first render?");
internal static readonly MelonPreferences_Entry<bool> EntryDebugShowShadow =
Category.CreateEntry("Debug Show Shadow", false,
description: "Should the shadow clone be shown?");
internal static readonly MelonPreferences_Entry<bool> EntryDebugShowInFront =
Category.CreateEntry("Debug Show in Front", false,
description: "Should the shadow clone be shown in front?");
#endregion #endregion
@ -46,5 +54,7 @@ public static class ModSettings
TransformHiderManager.s_DisallowFprExclusions = EntryDontRespectFPR.Value; TransformHiderManager.s_DisallowFprExclusions = EntryDontRespectFPR.Value;
TransformHiderManager.s_DebugHeadHide = EntryDebugHeadHide.Value; TransformHiderManager.s_DebugHeadHide = EntryDebugHeadHide.Value;
ShadowCloneManager.s_CopyMaterialsToShadow = EntryCopyMaterialToShadow.Value; ShadowCloneManager.s_CopyMaterialsToShadow = EntryCopyMaterialToShadow.Value;
ShadowCloneManager.s_DebugShowShadow = EntryDebugShowShadow.Value;
ShadowCloneManager.s_DebugShowInFront = EntryDebugShowInFront.Value;
} }
} }

View file

@ -8,4 +8,5 @@ public interface IShadowClone : IDisposable
bool Process(); bool Process();
void RenderForShadow(); void RenderForShadow();
void RenderForUiCulling(); void RenderForUiCulling();
void ResetMainMesh();
} }

View file

@ -28,6 +28,8 @@ public struct MeshShadowClone : IShadowClone
#region IShadowClone Methods #region IShadowClone Methods
public void ResetMainMesh(){}
public bool IsValid => _mainMesh != null && _shadowMesh != null; public bool IsValid => _mainMesh != null && _shadowMesh != null;
public MeshShadowClone(MeshRenderer meshRenderer) public MeshShadowClone(MeshRenderer meshRenderer)
@ -88,7 +90,9 @@ public struct MeshShadowClone : IShadowClone
public void RenderForShadow() public void RenderForShadow()
{ {
_shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; _shadowMesh.shadowCastingMode = ShadowCloneManager.s_DebugShowShadow
? ShadowCastingMode.On : ShadowCastingMode.ShadowsOnly;
_shadowMesh.forceRenderingOff = !_shouldCastShadows; _shadowMesh.forceRenderingOff = !_shouldCastShadows;
// shadow casting needs clone to have original materials (uv discard) // shadow casting needs clone to have original materials (uv discard)

View file

@ -8,6 +8,8 @@ public class SkinnedShadowClone : IShadowClone
{ {
private static readonly int s_SourceBufferId = Shader.PropertyToID("_sourceBuffer"); private static readonly int s_SourceBufferId = Shader.PropertyToID("_sourceBuffer");
private static readonly int s_TargetBufferId = Shader.PropertyToID("_targetBuffer"); 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_SourceBufferLayoutId = Shader.PropertyToID("_sourceBufferLayout");
private static readonly int s_SourceRootMatrix = Shader.PropertyToID("_rootBoneMatrix"); private static readonly int s_SourceRootMatrix = Shader.PropertyToID("_rootBoneMatrix");
@ -26,6 +28,7 @@ public class SkinnedShadowClone : IShadowClone
// clone copying // clone copying
private GraphicsBuffer _graphicsBuffer; private GraphicsBuffer _graphicsBuffer;
private GraphicsBuffer _targetBuffer; private GraphicsBuffer _targetBuffer;
private ComputeBuffer _computeBuffer;
private int _threadGroups; private int _threadGroups;
private int _bufferLayout; private int _bufferLayout;
@ -39,7 +42,7 @@ public class SkinnedShadowClone : IShadowClone
// anything player can touch is suspect to death // anything player can touch is suspect to death
public bool IsValid => _mainMesh != null && _shadowMesh != null && _rootBone != null; public bool IsValid => _mainMesh != null && _shadowMesh != null && _rootBone != null;
internal SkinnedShadowClone(SkinnedMeshRenderer renderer) internal SkinnedShadowClone(SkinnedMeshRenderer renderer, FPRExclusion exclusion)
{ {
_mainMesh = renderer; _mainMesh = renderer;
@ -52,11 +55,28 @@ public class SkinnedShadowClone : IShadowClone
return; // no mesh! 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; _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, _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.rootBone;
_rootBone ??= _mainMesh.transform; // fallback to transform if no root bone _rootBone ??= _mainMesh.transform; // fallback to transform if no root bone
@ -133,6 +153,8 @@ public class SkinnedShadowClone : IShadowClone
_graphicsBuffer = null; _graphicsBuffer = null;
_targetBuffer?.Dispose(); _targetBuffer?.Dispose();
_targetBuffer = null; _targetBuffer = null;
_computeBuffer?.Dispose();
_computeBuffer = null;
} }
#endregion #endregion
@ -151,7 +173,7 @@ public class SkinnedShadowClone : IShadowClone
if (mesh.HasVertexAttribute(VertexAttribute.Tangent)) _bufferLayout += 4; if (mesh.HasVertexAttribute(VertexAttribute.Tangent)) _bufferLayout += 4;
_bufferLayout *= 4; // 4 bytes per float _bufferLayout *= 4; // 4 bytes per float
const float xThreadGroups = 64f; const float xThreadGroups = 32f;
_threadGroups = Mathf.CeilToInt(mesh.vertexCount / xThreadGroups); _threadGroups = Mathf.CeilToInt(mesh.vertexCount / xThreadGroups);
_mainMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw; _mainMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
@ -162,10 +184,66 @@ public class SkinnedShadowClone : IShadowClone
//Debug.Log($"Initialized! BufferLayout: {_bufferLayout}, GraphicsBuffer: {_graphicsBuffer != null}, TargetBuffer: {_targetBuffer != null}"); //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<int> 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() private void ResetShadowClone()
{ {
_shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; if (ShadowCloneManager.s_DebugShowShadow)
_shadowMesh.forceRenderingOff = !_shouldCastShadows; {
_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) // shadow casting needs clone to have original materials (uv discard)
// we also want to respect material swaps... but this is fucking slow :( // we also want to respect material swaps... but this is fucking slow :(
@ -173,12 +251,7 @@ public class SkinnedShadowClone : IShadowClone
if (!ShadowCloneManager.s_CopyMaterialsToShadow) if (!ShadowCloneManager.s_CopyMaterialsToShadow)
return; return;
if (_hasShadowMaterials) _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials;
{
_shadowMesh.sharedMaterials = _mainMesh.sharedMaterials;
_hasShadowMaterials = false;
}
UpdateCloneMaterialProperties(); UpdateCloneMaterialProperties();
} }
@ -188,12 +261,10 @@ public class SkinnedShadowClone : IShadowClone
_shadowMesh.forceRenderingOff = false; _shadowMesh.forceRenderingOff = false;
// UI culling needs clone to have write-to-depth shader // UI culling needs clone to have write-to-depth shader
if (_hasShadowMaterials) return;
_shadowMesh.sharedMaterials = _shadowMaterials; _shadowMesh.sharedMaterials = _shadowMaterials;
_hasShadowMaterials = true;
// Not needed- MaterialPropertyBlock applied to renderer in RenderForShadow // Not needed- MaterialPropertyBlock applied to renderer in RenderForShadow
//UpdateCloneMaterialProperties(); UpdateCloneMaterialProperties();
} }
private void RenderShadowClone() private void RenderShadowClone()
@ -205,6 +276,10 @@ public class SkinnedShadowClone : IShadowClone
ShadowCloneHelper.shader.SetMatrix(s_SourceRootMatrix, rootMatrix); ShadowCloneHelper.shader.SetMatrix(s_SourceRootMatrix, rootMatrix);
ShadowCloneHelper.shader.SetBuffer(0, s_SourceBufferId, _graphicsBuffer); ShadowCloneHelper.shader.SetBuffer(0, s_SourceBufferId, _graphicsBuffer);
ShadowCloneHelper.shader.SetBuffer(0, s_TargetBufferId, _targetBuffer); 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.SetInt(s_SourceBufferLayoutId, _bufferLayout);
ShadowCloneHelper.shader.Dispatch(0, _threadGroups, 1, 1); ShadowCloneHelper.shader.Dispatch(0, _threadGroups, 1, 1);
_graphicsBuffer.Release(); _graphicsBuffer.Release();

View file

@ -35,11 +35,14 @@ public class ShadowCloneManager : MonoBehaviour
// Settings // Settings
internal static bool s_CopyMaterialsToShadow = true; internal static bool s_CopyMaterialsToShadow = true;
internal static bool s_DebugShowShadow = false;
internal static bool s_DebugShowInFront = false;
private static bool s_UseShadowToCullUi; private static bool s_UseShadowToCullUi;
private const string ShadowCullUiSettingName = "ExperimentalAvatarOverrenderUI"; private const string ShadowCullUiSettingName = "ExperimentalAvatarOverrenderUI";
// Implementation // Implementation
private bool _hasRenderedThisFrame; private bool _hasRenderedThisFrame;
public static readonly List<FPRExclusion> s_Exclusions = new();
// Shadow Clones // Shadow Clones
private readonly List<IShadowClone> s_ShadowClones = new(); private readonly List<IShadowClone> s_ShadowClones = new();
@ -64,15 +67,17 @@ public class ShadowCloneManager : MonoBehaviour
UpdatePlayerCameras(); UpdatePlayerCameras();
s_CopyMaterialsToShadow = ModSettings.EntryCopyMaterialToShadow.Value; s_CopyMaterialsToShadow = ModSettings.EntryCopyMaterialToShadow.Value;
s_DebugShowShadow = ModSettings.EntryDebugShowShadow.Value;
s_DebugShowInFront = ModSettings.EntryDebugShowInFront.Value;
s_UseShadowToCullUi = MetaPort.Instance.settings.GetSettingsBool(ShadowCullUiSettingName); s_UseShadowToCullUi = MetaPort.Instance.settings.GetSettingsBool(ShadowCullUiSettingName);
MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged); MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged);
} }
private void OnEnable() private void OnEnable()
=> Camera.onPreCull += MyOnPreCull; => Camera.onPreRender += MyOnPreCull;
private void OnDisable() private void OnDisable()
=> Camera.onPreCull -= MyOnPreCull; => Camera.onPreRender -= MyOnPreCull;
private void OnDestroy() private void OnDestroy()
{ {
@ -86,12 +91,25 @@ public class ShadowCloneManager : MonoBehaviour
private void Update() private void Update()
{ {
_hasRenderedThisFrame = false; _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) private void MyOnPreCull(Camera cam)
{ {
bool forceRenderForUiCull = s_UseShadowToCullUi && cam == s_UiCamera; //bool forceRenderForUiCull = s_UseShadowToCullUi && cam == s_UiCamera;
if (_hasRenderedThisFrame && !forceRenderForUiCull) if (cam != s_MainCamera)
return; return;
_hasRenderedThisFrame = true; _hasRenderedThisFrame = true;
@ -109,17 +127,13 @@ public class ShadowCloneManager : MonoBehaviour
} }
if (!clone.Process()) continue; // not ready yet or disabled if (!clone.Process()) continue; // not ready yet or disabled
if (forceRenderForUiCull) clone.RenderForShadow(); // first cam to render
clone.RenderForUiCulling(); // last cam to render
else
clone.RenderForShadow(); // first cam to render
} }
_stopWatch.Stop(); _stopWatch.Stop();
if (_debugShadowProcessingTime) Debug.Log($"ShadowCloneManager.MyOnPreCull({forceRenderForUiCull}) took {_stopWatch.ElapsedMilliseconds}ms");
} }
#endregion #endregion
#region Game Events #region Game Events
@ -136,7 +150,12 @@ public class ShadowCloneManager : MonoBehaviour
private void OnSettingsBoolChanged(string settingName, bool settingValue) private void OnSettingsBoolChanged(string settingName, bool settingValue)
{ {
if (settingName == ShadowCullUiSettingName) if (settingName == ShadowCullUiSettingName)
{
s_UseShadowToCullUi = settingValue; 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 __) private void OnVRModeSwitchCompleted(bool _, Camera __)
@ -152,6 +171,7 @@ public class ShadowCloneManager : MonoBehaviour
{ {
s_MainCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>(); s_MainCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
s_UiCamera = s_MainCamera.transform.Find("_UICamera").GetComponent<Camera>(); s_UiCamera = s_MainCamera.transform.Find("_UICamera").GetComponent<Camera>();
//s_PortableCamera = PortableCamera.Instance.cameraComponent; //s_PortableCamera = PortableCamera.Instance.cameraComponent;
} }
@ -159,11 +179,11 @@ public class ShadowCloneManager : MonoBehaviour
#region Static Helpers #region Static Helpers
internal static IShadowClone CreateShadowClone(Renderer renderer) internal static IShadowClone CreateShadowClone(Renderer renderer, FPRExclusion exclusion)
{ {
return renderer switch return renderer switch
{ {
SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedShadowClone(skinnedMeshRenderer), SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedShadowClone(skinnedMeshRenderer, exclusion),
MeshRenderer meshRenderer => new MeshShadowClone(meshRenderer), MeshRenderer meshRenderer => new MeshShadowClone(meshRenderer),
_ => null _ => null
}; };
@ -175,6 +195,14 @@ public class ShadowCloneManager : MonoBehaviour
shadowClone.transform.SetParent(meshRenderer.transform, false); shadowClone.transform.SetParent(meshRenderer.transform, false);
shadowClone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); shadowClone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
shadowClone.transform.localScale = Vector3.one; 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<MeshRenderer>(); MeshRenderer newMesh = shadowClone.AddComponent<MeshRenderer>();
MeshFilter newMeshFilter = shadowClone.AddComponent<MeshFilter>(); MeshFilter newMeshFilter = shadowClone.AddComponent<MeshFilter>();
@ -188,6 +216,9 @@ public class ShadowCloneManager : MonoBehaviour
newMeshFilter.sharedMesh = meshRenderer.sharedMesh; newMeshFilter.sharedMesh = meshRenderer.sharedMesh;
newMesh.sharedMaterials = meshRenderer.sharedMaterials; newMesh.sharedMaterials = meshRenderer.sharedMaterials;
// copy probe anchor
newMesh.probeAnchor = meshRenderer.probeAnchor;
return (newMesh, newMeshFilter); return (newMesh, newMeshFilter);
} }
@ -209,6 +240,9 @@ public class ShadowCloneManager : MonoBehaviour
// copy mesh and materials // copy mesh and materials
newMeshFilter.sharedMesh = meshRenderer.GetComponent<MeshFilter>().sharedMesh; newMeshFilter.sharedMesh = meshRenderer.GetComponent<MeshFilter>().sharedMesh;
newMesh.sharedMaterials = meshRenderer.sharedMaterials; newMesh.sharedMaterials = meshRenderer.sharedMaterials;
// copy probe anchor
newMesh.probeAnchor = meshRenderer.probeAnchor;
return (newMesh, newMeshFilter); return (newMesh, newMeshFilter);
} }

View file

@ -39,6 +39,8 @@ public static class ShadowCloneHelper
return; return;
} }
ShadowCloneMod.Logger.Msg($"Found {renderers.Length} renderers. Processing...");
// create shadow clones // create shadow clones
ProcessRenderers(renderers, avatar.transform, headBone); ProcessRenderers(renderers, avatar.transform, headBone);
} }
@ -48,6 +50,7 @@ public static class ShadowCloneHelper
Stopwatch sw = Stopwatch.StartNew(); Stopwatch sw = Stopwatch.StartNew();
IReadOnlyDictionary<Transform, FPRExclusion> exclusions = CollectTransformToExclusionMap(root, headBone); IReadOnlyDictionary<Transform, FPRExclusion> exclusions = CollectTransformToExclusionMap(root, headBone);
var exclusion = headBone.gameObject.GetComponent<FPRExclusion>();
// log current time // log current time
ShadowCloneMod.Logger.Msg($"CollectTransformToExclusionMap in {sw.ElapsedMilliseconds}ms"); ShadowCloneMod.Logger.Msg($"CollectTransformToExclusionMap in {sw.ElapsedMilliseconds}ms");
@ -58,12 +61,12 @@ public static class ShadowCloneHelper
if (ModSettings.EntryUseShadowClone.Value) if (ModSettings.EntryUseShadowClone.Value)
{ {
IShadowClone clone = ShadowCloneManager.CreateShadowClone(renderer); IShadowClone clone = ShadowCloneManager.CreateShadowClone(renderer, exclusion);
if (clone != null) ShadowCloneManager.Instance.AddShadowClone(clone); if (clone != null) ShadowCloneManager.Instance.AddShadowClone(clone);
} }
ITransformHider hider = TransformHiderManager.CreateTransformHider(renderer, exclusions); // ITransformHider hider = TransformHiderManager.CreateTransformHider(renderer, exclusions);
if (hider != null) TransformHiderManager.Instance.AddTransformHider(hider); // if (hider != null) TransformHiderManager.Instance.AddTransformHider(hider);
} }
sw.Stop(); sw.Stop();
@ -143,9 +146,9 @@ public static class ShadowCloneHelper
// shadow clone optimizations (always MeshRenderer) // shadow clone optimizations (always MeshRenderer)
if (isShadowClone) if (isShadowClone)
{ {
renderer.receiveShadows = false; // renderer.receiveShadows = false;
renderer.lightProbeUsage = LightProbeUsage.Off; // renderer.lightProbeUsage = LightProbeUsage.Off;
renderer.reflectionProbeUsage = ReflectionProbeUsage.Off; // renderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
return; return;
} }

Binary file not shown.

View file

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

View file

@ -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<SkinnedMeshRenderer>().forceRenderingOff = true;
}
}

View file

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

View file

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

View file

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

80
MirrorClone/Main.cs Normal file
View file

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

View file

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

View file

@ -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<Animator>();
if (!_mirrorClone.TryGetComponent(out _mirrorAnimator))
_mirrorAnimator = gameObject.AddComponent<Animator>();
_mirrorAnimator.runtimeAnimatorController = baseAnimator.runtimeAnimatorController;
_animatorManager._copyAnimator = _mirrorAnimator; // thank you for existing
var cameras = PlayerSetup.Instance.GetComponentsInChildren<Camera>(true);
foreach (var camera in cameras)
{
// hide PlayerClone layer from all cameras
camera.cullingMask &= ~(1 << CVRLayers.PlayerClone);
}
var mirrors = Resources.FindObjectsOfTypeAll<CVRMirror>();
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<SkinnedMeshRenderer>(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<Component>(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<Transform>(true);
// var mirrorCloneTransforms = _mirrorClone.GetComponentsInChildren<Transform>(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
}

View file

@ -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<bool> 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)
{
}
}

View file

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

16
MirrorClone/README.md Normal file
View file

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

23
MirrorClone/format.json Normal file
View file

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

View file

@ -35,6 +35,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MirrorClone", "MirrorClone\
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterShadowClone", "BetterShadowClone\BetterShadowClone.csproj", "{D0C40987-AF16-490A-9304-F99D5A5A774C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterShadowClone", "BetterShadowClone\BetterShadowClone.csproj", "{D0C40987-AF16-490A-9304-F99D5A5A774C}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{D0C40987-AF16-490A-9304-F99D5A5A774C}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

53
Nevermind/Main.cs Normal file
View file

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

View file

@ -1,4 +1,4 @@
using Nevermind.Properties; using NAK.Nevermind.Properties;
using MelonLoader; using MelonLoader;
using System.Reflection; using System.Reflection;
@ -20,10 +20,9 @@ using System.Reflection;
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonOptionalDependencies("BTKUILib")]
[assembly: HarmonyDontPatchAll] [assembly: HarmonyDontPatchAll]
namespace Nevermind.Properties; namespace NAK.Nevermind.Properties;
internal static class AssemblyInfoParams internal static class AssemblyInfoParams
{ {
public const string Version = "1.0.0"; public const string Version = "1.0.0";

23
Nevermind/format.json Normal file
View file

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

View file

@ -23,32 +23,33 @@ public class ObjectSyncBridge : MonoBehaviour
_physicsGun.OnPreGrabbedObject = o => _physicsGun.OnPreGrabbedObject = o =>
{ {
bool canTakeOwnership = false; bool canTakeOwnership = false;
//
CVRObjectSync objectSync = o.GetComponentInParent<CVRObjectSync>(); CVRObjectSync objectSync = o.GetComponentInParent<CVRObjectSync>();
if (objectSync != null // if (objectSync != null
&& (objectSync.SyncType == 0 // check if physics synced or synced by us // && (objectSync.SyncType == 0 // check if physics synced or synced by us
|| objectSync.SyncedByMe)) // || objectSync.SyncedByMe))
canTakeOwnership = true; // canTakeOwnership = true;
//
CVRSpawnable spawnable = o.GetComponentInParent<CVRSpawnable>(); CVRSpawnable spawnable = o.GetComponentInParent<CVRSpawnable>();
if (spawnable != null) // if (spawnable != null)
{ // {
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == spawnable.instanceId); // CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == spawnable.instanceId);
if (propData != null // if (propData != null
&& (propData.syncType == 0 // check if physics synced or synced by us // && (propData.syncType == 0 // check if physics synced or synced by us
|| propData.syncedBy == MetaPort.Instance.ownerId)) // || propData.syncedBy == MetaPort.Instance.ownerId))
canTakeOwnership = true; // canTakeOwnership = true;
} // }
//
CVRPickupObject pickup = o.GetComponentInParent<CVRPickupObject>(); CVRPickupObject pickup = o.GetComponentInParent<CVRPickupObject>();
if (pickup != null // if (pickup != null
&& (pickup.grabbedBy == MetaPort.Instance.ownerId // check if already grabbed by us // && (pickup.grabbedBy == MetaPort.Instance.ownerId // check if already grabbed by us
|| pickup.grabbedBy == "" || !pickup.disallowTheft)) // check if not grabbed or allows theft // || pickup.grabbedBy == "" || !pickup.disallowTheft)) // check if not grabbed or allows theft
canTakeOwnership = true; // canTakeOwnership = true;
//
if (!canTakeOwnership // if we can't take ownership, don't grab, unless there is no syncing at all (local object) // if (!canTakeOwnership // if we can't take ownership, don't grab, unless there is no syncing at all (local object)
&& (objectSync || spawnable || pickup )) // && (objectSync || spawnable || pickup ))
return false; // return false;
if (pickup) if (pickup)
{ {

View file

@ -1,8 +1,8 @@
{ {
"_id": 147, "_id": 147,
"name": "PropUndoButton", "name": "PropUndoButton",
"modversion": "1.0.1", "modversion": "1.0.2",
"gameversion": "2022r170p1", "gameversion": "2024r175",
"loaderversion": "0.6.1", "loaderversion": "0.6.1",
"modtype": "Mod", "modtype": "Mod",
"author": "NotAKidoS", "author": "NotAKidoS",
@ -16,8 +16,8 @@
"requirements": [ "requirements": [
"None" "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/", "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" "embedcolor": "#00FFFF"
} }

View file

@ -70,7 +70,7 @@ internal static class CameraLogic
return; return;
ThirdPerson.Logger.Msg("Copying active camera settings & components."); ThirdPerson.Logger.Msg("Copying active camera settings & components.");
CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam); CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam, true);
if (!CheckIsRestricted()) if (!CheckIsRestricted())
return; return;

View file

@ -1,11 +0,0 @@
using NAK.BetterShadowClone;
namespace NAK.ThirdPerson.Integrations;
public static class BSCAddon
{
public static void Initialize()
{
ShadowCloneMod.wantsToHideHead += CameraLogic.ShouldNotHideHead_ThirdPerson;
}
}

View file

@ -1,7 +1,6 @@
using MelonLoader; using MelonLoader;
using NAK.ThirdPerson.Properties; using NAK.ThirdPerson.Properties;
using System.Reflection; using System.Reflection;
using CurvedUI;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
@ -24,11 +23,10 @@ using CurvedUI;
[assembly: MelonColor(255, 246, 25, 97)] [assembly: MelonColor(255, 246, 25, 97)]
[assembly: MelonAuthorColor(255, 158, 21, 32)] [assembly: MelonAuthorColor(255, 158, 21, 32)]
[assembly: HarmonyDontPatchAll] [assembly: HarmonyDontPatchAll]
[assembly: MelonOptionalDependencies("BetterShadowClone")]
namespace NAK.ThirdPerson.Properties; namespace NAK.ThirdPerson.Properties;
internal static class AssemblyInfoParams internal static class AssemblyInfoParams
{ {
public const string Version = "1.0.7"; public const string Version = "1.0.8";
public const string Author = "Davi & NotAKidoS"; public const string Author = "Davi & NotAKidoS";
} }

View file

@ -14,17 +14,6 @@ public class ThirdPerson : MelonMod
Patches.Apply(HarmonyInstance); Patches.Apply(HarmonyInstance);
MelonCoroutines.Start(SetupCamera()); 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() public override void OnUpdate()

View file

@ -1,6 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk" />
<ItemGroup>
<ProjectReference Include="..\BetterShadowClone\BetterShadowClone.csproj" />
</ItemGroup>
</Project>

View file

@ -2,8 +2,8 @@
{ {
"_id": 16, "_id": 16,
"name": "ThirdPerson", "name": "ThirdPerson",
"modversion": "1.0.7", "modversion": "1.0.8",
"gameversion": "2023r173", "gameversion": "2024r175",
"loaderversion": "0.6.1", "loaderversion": "0.6.1",
"modtype": "Mod", "modtype": "Mod",
"author": "Davi & NotAKidoS", "author": "Davi & NotAKidoS",
@ -14,9 +14,9 @@
"third person" "third person"
], ],
"requirements": [], "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", "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" "embedcolor": "#F61961"
} }
] ]

119
YouAreAClone/Main.cs Normal file
View file

@ -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<ComputeShader>(BoneHiderComputePath);
shader.hideFlags |= HideFlags.DontUnloadUnusedAsset;
TransformHiderManager.shader = shader;
Logger.Msg($"Loaded {BoneHiderComputePath}!");
// load shadow clone shader
ComputeShader shadowCloneCompute = assetBundle.LoadAsset<ComputeShader>(ShadowCloneComputePath);
shadowCloneCompute.hideFlags |= HideFlags.DontUnloadUnusedAsset;
ShadowCloneHelper.shader = shadowCloneCompute;
Logger.Msg($"Loaded {ShadowCloneComputePath}!");
// load shadow clone material
Shader shadowCloneShader = assetBundle.LoadAsset<Shader>(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
}

View file

@ -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<bool> EntryEnabled =
Category.CreateEntry("Enabled", true,
description: "Enable Mirror Clone.");
internal static readonly MelonPreferences_Entry<bool> EntryUseShadowClone =
Category.CreateEntry("Use Shadow Clone", true,
description: "Should you have shadow clones?");
internal static readonly MelonPreferences_Entry<bool> 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<bool> EntryDontRespectFPR =
Category.CreateEntry("Dont Respect FPR", false,
description: "Should the transform hider not respect FPR?");
internal static readonly MelonPreferences_Entry<bool> EntryDebugHeadHide =
Category.CreateEntry("Debug Head Hide", false,
description: "Should head be hidden for first render?");
internal static readonly MelonPreferences_Entry<bool> EntryDebugShowShadow =
Category.CreateEntry("Debug Show Shadow", false,
description: "Should the shadow clone be shown?");
internal static readonly MelonPreferences_Entry<bool> 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;
}
}

View file

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

View file

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