diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.Init.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.Util.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.cs diff --git a/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs b/.Deprecated/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs diff --git a/AvatarCloneTest/AvatarCloneTest.csproj b/.Deprecated/AvatarCloneTest/AvatarCloneTest.csproj similarity index 100% rename from AvatarCloneTest/AvatarCloneTest.csproj rename to .Deprecated/AvatarCloneTest/AvatarCloneTest.csproj diff --git a/AvatarCloneTest/Main.cs b/.Deprecated/AvatarCloneTest/Main.cs similarity index 100% rename from AvatarCloneTest/Main.cs rename to .Deprecated/AvatarCloneTest/Main.cs diff --git a/AvatarCloneTest/Patches.cs b/.Deprecated/AvatarCloneTest/Patches.cs similarity index 100% rename from AvatarCloneTest/Patches.cs rename to .Deprecated/AvatarCloneTest/Patches.cs diff --git a/AvatarCloneTest/Properties/AssemblyInfo.cs b/.Deprecated/AvatarCloneTest/Properties/AssemblyInfo.cs similarity index 100% rename from AvatarCloneTest/Properties/AssemblyInfo.cs rename to .Deprecated/AvatarCloneTest/Properties/AssemblyInfo.cs diff --git a/AvatarCloneTest/README.md b/.Deprecated/AvatarCloneTest/README.md similarity index 100% rename from AvatarCloneTest/README.md rename to .Deprecated/AvatarCloneTest/README.md diff --git a/AvatarCloneTest/format.json b/.Deprecated/AvatarCloneTest/format.json similarity index 100% rename from AvatarCloneTest/format.json rename to .Deprecated/AvatarCloneTest/format.json diff --git a/ChatBoxExtensions/ChatBoxExtensions.csproj b/.Deprecated/ChatBoxExtensions/ChatBoxExtensions.csproj similarity index 100% rename from ChatBoxExtensions/ChatBoxExtensions.csproj rename to .Deprecated/ChatBoxExtensions/ChatBoxExtensions.csproj diff --git a/ChatBoxExtensions/HarmonyPatches.cs b/.Deprecated/ChatBoxExtensions/HarmonyPatches.cs similarity index 100% rename from ChatBoxExtensions/HarmonyPatches.cs rename to .Deprecated/ChatBoxExtensions/HarmonyPatches.cs diff --git a/ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs b/.Deprecated/ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs similarity index 100% rename from ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs rename to .Deprecated/ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs diff --git a/ChatBoxExtensions/Integrations/Base.cs b/.Deprecated/ChatBoxExtensions/Integrations/Base.cs similarity index 100% rename from ChatBoxExtensions/Integrations/Base.cs rename to .Deprecated/ChatBoxExtensions/Integrations/Base.cs diff --git a/ChatBoxExtensions/Integrations/ChatBox.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChatBox.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChatBox.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChatBox.cs diff --git a/ChatBoxExtensions/Integrations/ChilloutVRAAS.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChilloutVRAAS.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChilloutVRAAS.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChilloutVRAAS.cs diff --git a/ChatBoxExtensions/Integrations/ChilloutVRBase.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChilloutVRBase.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChilloutVRBase.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChilloutVRBase.cs diff --git a/ChatBoxExtensions/Integrations/ChilloutVRInput.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChilloutVRInput.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChilloutVRInput.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChilloutVRInput.cs diff --git a/ChatBoxExtensions/Integrations/Commands.cs b/.Deprecated/ChatBoxExtensions/Integrations/Commands.cs similarity index 100% rename from ChatBoxExtensions/Integrations/Commands.cs rename to .Deprecated/ChatBoxExtensions/Integrations/Commands.cs diff --git a/ChatBoxExtensions/Integrations/PlayerRagdollMod.cs b/.Deprecated/ChatBoxExtensions/Integrations/PlayerRagdollMod.cs similarity index 100% rename from ChatBoxExtensions/Integrations/PlayerRagdollMod.cs rename to .Deprecated/ChatBoxExtensions/Integrations/PlayerRagdollMod.cs diff --git a/ChatBoxExtensions/Main.cs b/.Deprecated/ChatBoxExtensions/Main.cs similarity index 100% rename from ChatBoxExtensions/Main.cs rename to .Deprecated/ChatBoxExtensions/Main.cs diff --git a/ChatBoxExtensions/Properties/AssemblyInfo.cs b/.Deprecated/ChatBoxExtensions/Properties/AssemblyInfo.cs similarity index 100% rename from ChatBoxExtensions/Properties/AssemblyInfo.cs rename to .Deprecated/ChatBoxExtensions/Properties/AssemblyInfo.cs diff --git a/ChatBoxExtensions/format.json b/.Deprecated/ChatBoxExtensions/format.json similarity index 100% rename from ChatBoxExtensions/format.json rename to .Deprecated/ChatBoxExtensions/format.json diff --git a/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj b/.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj similarity index 100% rename from CVRLuaToolsExtension/CVRLuaToolsExtension.csproj rename to .Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs diff --git a/CVRLuaToolsExtension/Main.cs b/.Experimental/CVRLuaToolsExtension/Main.cs similarity index 100% rename from CVRLuaToolsExtension/Main.cs rename to .Experimental/CVRLuaToolsExtension/Main.cs diff --git a/CVRLuaToolsExtension/Properties/AssemblyInfo.cs b/.Experimental/CVRLuaToolsExtension/Properties/AssemblyInfo.cs similarity index 100% rename from CVRLuaToolsExtension/Properties/AssemblyInfo.cs rename to .Experimental/CVRLuaToolsExtension/Properties/AssemblyInfo.cs diff --git a/CVRLuaToolsExtension/README.md b/.Experimental/CVRLuaToolsExtension/README.md similarity index 100% rename from CVRLuaToolsExtension/README.md rename to .Experimental/CVRLuaToolsExtension/README.md diff --git a/CVRLuaToolsExtension/format.json b/.Experimental/CVRLuaToolsExtension/format.json similarity index 100% rename from CVRLuaToolsExtension/format.json rename to .Experimental/CVRLuaToolsExtension/format.json diff --git a/LuaNetworkVariables/LuaNetworkVariables.csproj b/.Experimental/LuaNetworkVariables/LuaNetworkVariables.csproj similarity index 100% rename from LuaNetworkVariables/LuaNetworkVariables.csproj rename to .Experimental/LuaNetworkVariables/LuaNetworkVariables.csproj diff --git a/LuaNetworkVariables/Main.cs b/.Experimental/LuaNetworkVariables/Main.cs similarity index 100% rename from LuaNetworkVariables/Main.cs rename to .Experimental/LuaNetworkVariables/Main.cs diff --git a/LuaNetworkVariables/NetLuaModule.cs b/.Experimental/LuaNetworkVariables/NetLuaModule.cs similarity index 100% rename from LuaNetworkVariables/NetLuaModule.cs rename to .Experimental/LuaNetworkVariables/NetLuaModule.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaEventContext.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs diff --git a/LuaNetworkVariables/Patches.cs b/.Experimental/LuaNetworkVariables/Patches.cs similarity index 100% rename from LuaNetworkVariables/Patches.cs rename to .Experimental/LuaNetworkVariables/Patches.cs diff --git a/LuaNetworkVariables/Properties/AssemblyInfo.cs b/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs similarity index 100% rename from LuaNetworkVariables/Properties/AssemblyInfo.cs rename to .Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs diff --git a/LuaNetworkVariables/README.md b/.Experimental/LuaNetworkVariables/README.md similarity index 100% rename from LuaNetworkVariables/README.md rename to .Experimental/LuaNetworkVariables/README.md diff --git a/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs similarity index 100% rename from LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs rename to .Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs diff --git a/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs similarity index 100% rename from LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs rename to .Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs diff --git a/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs similarity index 100% rename from LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs rename to .Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs diff --git a/LuaNetworkVariables/format.json b/.Experimental/LuaNetworkVariables/format.json similarity index 100% rename from LuaNetworkVariables/format.json rename to .Experimental/LuaNetworkVariables/format.json diff --git a/LuaTTS/LuaTTS.csproj b/.Experimental/LuaTTS/LuaTTS.csproj similarity index 100% rename from LuaTTS/LuaTTS.csproj rename to .Experimental/LuaTTS/LuaTTS.csproj diff --git a/LuaTTS/Main.cs b/.Experimental/LuaTTS/Main.cs similarity index 100% rename from LuaTTS/Main.cs rename to .Experimental/LuaTTS/Main.cs diff --git a/LuaTTS/Patches.cs b/.Experimental/LuaTTS/Patches.cs similarity index 100% rename from LuaTTS/Patches.cs rename to .Experimental/LuaTTS/Patches.cs diff --git a/LuaTTS/Properties/AssemblyInfo.cs b/.Experimental/LuaTTS/Properties/AssemblyInfo.cs similarity index 100% rename from LuaTTS/Properties/AssemblyInfo.cs rename to .Experimental/LuaTTS/Properties/AssemblyInfo.cs diff --git a/LuaTTS/README.md b/.Experimental/LuaTTS/README.md similarity index 100% rename from LuaTTS/README.md rename to .Experimental/LuaTTS/README.md diff --git a/LuaTTS/TTSLuaModule.cs b/.Experimental/LuaTTS/TTSLuaModule.cs similarity index 100% rename from LuaTTS/TTSLuaModule.cs rename to .Experimental/LuaTTS/TTSLuaModule.cs diff --git a/LuaTTS/format.json b/.Experimental/LuaTTS/format.json similarity index 100% rename from LuaTTS/format.json rename to .Experimental/LuaTTS/format.json diff --git a/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs b/.Experimental/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs similarity index 100% rename from OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs rename to .Experimental/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon.cs b/.Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon.cs similarity index 100% rename from OriginShift/Integrations/BTKUI/BtkuiAddon.cs rename to .Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon.cs diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs b/.Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs similarity index 100% rename from OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs rename to .Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs diff --git a/OriginShift/Integrations/Ragdoll/RagdollAddon.cs b/.Experimental/OriginShift/Integrations/Ragdoll/RagdollAddon.cs similarity index 100% rename from OriginShift/Integrations/Ragdoll/RagdollAddon.cs rename to .Experimental/OriginShift/Integrations/Ragdoll/RagdollAddon.cs diff --git a/OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs b/.Experimental/OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs similarity index 100% rename from OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs rename to .Experimental/OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs diff --git a/OriginShift/Main.cs b/.Experimental/OriginShift/Main.cs similarity index 100% rename from OriginShift/Main.cs rename to .Experimental/OriginShift/Main.cs diff --git a/OriginShift/ModSettings.cs b/.Experimental/OriginShift/ModSettings.cs similarity index 100% rename from OriginShift/ModSettings.cs rename to .Experimental/OriginShift/ModSettings.cs diff --git a/OriginShift/Networking/ModNetwork.cs b/.Experimental/OriginShift/Networking/ModNetwork.cs similarity index 100% rename from OriginShift/Networking/ModNetwork.cs rename to .Experimental/OriginShift/Networking/ModNetwork.cs diff --git a/OriginShift/OriginShift.csproj b/.Experimental/OriginShift/OriginShift.csproj similarity index 100% rename from OriginShift/OriginShift.csproj rename to .Experimental/OriginShift/OriginShift.csproj diff --git a/OriginShift/OriginShift/Components/Chunk/ChunkController.cs b/.Experimental/OriginShift/OriginShift/Components/Chunk/ChunkController.cs similarity index 100% rename from OriginShift/OriginShift/Components/Chunk/ChunkController.cs rename to .Experimental/OriginShift/OriginShift/Components/Chunk/ChunkController.cs diff --git a/OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs b/.Experimental/OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs similarity index 100% rename from OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs rename to .Experimental/OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs diff --git a/OriginShift/OriginShift/Components/Chunk/ChunkListener.cs b/.Experimental/OriginShift/OriginShift/Components/Chunk/ChunkListener.cs similarity index 100% rename from OriginShift/OriginShift/Components/Chunk/ChunkListener.cs rename to .Experimental/OriginShift/OriginShift/Components/Chunk/ChunkListener.cs diff --git a/OriginShift/OriginShift/Components/OriginShiftController.cs b/.Experimental/OriginShift/OriginShift/Components/OriginShiftController.cs similarity index 100% rename from OriginShift/OriginShift/Components/OriginShiftController.cs rename to .Experimental/OriginShift/OriginShift/Components/OriginShiftController.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs diff --git a/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs b/.Experimental/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs similarity index 100% rename from OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs rename to .Experimental/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs diff --git a/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs b/.Experimental/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs similarity index 100% rename from OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs rename to .Experimental/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs diff --git a/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs b/.Experimental/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs similarity index 100% rename from OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs rename to .Experimental/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs diff --git a/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs b/.Experimental/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs similarity index 100% rename from OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs rename to .Experimental/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs diff --git a/OriginShift/OriginShift/OriginShiftManager.cs b/.Experimental/OriginShift/OriginShift/OriginShiftManager.cs similarity index 100% rename from OriginShift/OriginShift/OriginShiftManager.cs rename to .Experimental/OriginShift/OriginShift/OriginShiftManager.cs diff --git a/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftMonitor.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftMonitor.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftMonitor.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftMonitor.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs diff --git a/OriginShift/OriginShift/Utility/DebugTextDisplay.cs b/.Experimental/OriginShift/OriginShift/Utility/DebugTextDisplay.cs similarity index 100% rename from OriginShift/OriginShift/Utility/DebugTextDisplay.cs rename to .Experimental/OriginShift/OriginShift/Utility/DebugTextDisplay.cs diff --git a/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs b/.Experimental/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs similarity index 100% rename from OriginShift/OriginShift/Utility/RendererReflectionUtility.cs rename to .Experimental/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs diff --git a/OriginShift/Patches.cs b/.Experimental/OriginShift/Patches.cs similarity index 100% rename from OriginShift/Patches.cs rename to .Experimental/OriginShift/Patches.cs diff --git a/OriginShift/Properties/AssemblyInfo.cs b/.Experimental/OriginShift/Properties/AssemblyInfo.cs similarity index 100% rename from OriginShift/Properties/AssemblyInfo.cs rename to .Experimental/OriginShift/Properties/AssemblyInfo.cs diff --git a/OriginShift/README.md b/.Experimental/OriginShift/README.md similarity index 100% rename from OriginShift/README.md rename to .Experimental/OriginShift/README.md diff --git a/OriginShift/Resources/OriginShift-Icon-Active.png b/.Experimental/OriginShift/Resources/OriginShift-Icon-Active.png similarity index 100% rename from OriginShift/Resources/OriginShift-Icon-Active.png rename to .Experimental/OriginShift/Resources/OriginShift-Icon-Active.png diff --git a/OriginShift/Resources/OriginShift-Icon-Forced.png b/.Experimental/OriginShift/Resources/OriginShift-Icon-Forced.png similarity index 100% rename from OriginShift/Resources/OriginShift-Icon-Forced.png rename to .Experimental/OriginShift/Resources/OriginShift-Icon-Forced.png diff --git a/OriginShift/Resources/OriginShift-Icon-Inactive.png b/.Experimental/OriginShift/Resources/OriginShift-Icon-Inactive.png similarity index 100% rename from OriginShift/Resources/OriginShift-Icon-Inactive.png rename to .Experimental/OriginShift/Resources/OriginShift-Icon-Inactive.png diff --git a/OriginShift/format.json b/.Experimental/OriginShift/format.json similarity index 100% rename from OriginShift/format.json rename to .Experimental/OriginShift/format.json diff --git a/ScriptingSpoofer/Main.cs b/.Experimental/ScriptingSpoofer/Main.cs similarity index 100% rename from ScriptingSpoofer/Main.cs rename to .Experimental/ScriptingSpoofer/Main.cs diff --git a/ScriptingSpoofer/Properties/AssemblyInfo.cs b/.Experimental/ScriptingSpoofer/Properties/AssemblyInfo.cs similarity index 100% rename from ScriptingSpoofer/Properties/AssemblyInfo.cs rename to .Experimental/ScriptingSpoofer/Properties/AssemblyInfo.cs diff --git a/ScriptingSpoofer/README.md b/.Experimental/ScriptingSpoofer/README.md similarity index 100% rename from ScriptingSpoofer/README.md rename to .Experimental/ScriptingSpoofer/README.md diff --git a/ScriptingSpoofer/ScriptingSpoofer.csproj b/.Experimental/ScriptingSpoofer/ScriptingSpoofer.csproj similarity index 100% rename from ScriptingSpoofer/ScriptingSpoofer.csproj rename to .Experimental/ScriptingSpoofer/ScriptingSpoofer.csproj diff --git a/ScriptingSpoofer/format.json b/.Experimental/ScriptingSpoofer/format.json similarity index 100% rename from ScriptingSpoofer/format.json rename to .Experimental/ScriptingSpoofer/format.json diff --git a/.gitignore b/.gitignore index 2abd364..e3092f6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # NAK -.Experimental/ +.Blackbox/ # Nstrip & ManagedLibs stuff NStrip.exe diff --git a/BetterContentLoading/BetterContentLoading.csproj b/BetterContentLoading/BetterContentLoading.csproj deleted file mode 100644 index 67cef19..0000000 --- a/BetterContentLoading/BetterContentLoading.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net48 - NAK.BetterContentLoading - - - - ..\.ManagedLibs\BTKUILib.dll - - - ..\.ManagedLibs\TheClapper.dll - - - diff --git a/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs b/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs deleted file mode 100644 index 749483f..0000000 --- a/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs +++ /dev/null @@ -1,210 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Networking.IO.Instancing; -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using ABI_RC.Systems.GameEventSystem; -using NAK.BetterContentLoading.Queue; -using UnityEngine; - -namespace NAK.BetterContentLoading; - -// Download world -> connect to instance -> receive all Avatar data -// -> initial connection to instance -> receive all props data - -// We receive Prop download data only after we have connected to the instance. Avatar data we seem to receive -// prior to our initial connection event firing. - -public class BetterDownloadManager -{ - #region Singleton - - private static BetterDownloadManager _instance; - public static BetterDownloadManager Instance => _instance ??= new BetterDownloadManager(); - - #endregion Singleton - - #region Constructor - - private BetterDownloadManager() - { - _downloadProcessor = new DownloadProcessor(); - - _worldQueue = new WorldDownloadQueue(this); // Only one world at a time - _avatarQueue = new AvatarDownloadQueue(this); // Up to 3 avatars at once - _propQueue = new PropDownloadQueue(this); // Up to 2 props at once - - // Set to 100MBs by default - MaxDownloadBandwidth = 100 * 1024 * 1024; - - CVRGameEventSystem.Instance.OnConnected.AddListener(_ => - { - if (!Instances.IsReconnecting) OnInitialConnectionToInstance(); - }); - } - - #endregion Constructor - - #region Settings - - /// Log debug messages - public bool IsDebugEnabled { get; set; } = true; - - /// Prioritize friends first in download queue - public bool PrioritizeFriends { get; set; } = true; - - /// Prioritize content closest to player first in download queue - public bool PrioritizeDistance { get; set; } = true; - public float PriorityDownloadDistance { get; set; } = 25f; - - public int MaxDownloadBandwidth - { - get => _downloadProcessor.MaxDownloadBandwidth; - set => _downloadProcessor.MaxDownloadBandwidth = value; - } - - #endregion Settings - - private readonly DownloadProcessor _downloadProcessor; - - private readonly AvatarDownloadQueue _avatarQueue; - private readonly PropDownloadQueue _propQueue; - private readonly WorldDownloadQueue _worldQueue; - - #region Game Events - - private void OnInitialConnectionToInstance() - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg("Initial connection established."); - // await few seconds before chewing through the download queue, to allow for download priorities to be set - // once we have received most of the data from the server - } - - #endregion Game Events - - #region Public Queue Methods - - public void QueueAvatarDownload( - in DownloadInfo info, - string playerId, - CVRLoadingAvatarController loadingController) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Avatar Download:\n{info.GetLogString()}\n" + - $"PlayerId: {playerId}\n" + - $"LoadingController: {loadingController}"); - } - - _avatarQueue.QueueDownload(in info, playerId); - } - - /// - /// Queues a prop download. - /// - /// The download info. - /// The instance ID for the prop. - /// The user who spawned the prop. - public void QueuePropDownload( - in DownloadInfo info, - string instanceId, - string spawnerId) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Prop Download:\n{info.GetLogString()}\n" + - $"InstanceId: {instanceId}\n" + - $"SpawnerId: {spawnerId}"); - } - - _propQueue.QueueDownload(in info, instanceId, spawnerId); - } - - /// - /// Queues a world download. - /// - /// Download info. - /// Whether to load into this world once downloaded. - /// Whether the home world is requested. - public void QueueWorldDownload( - in DownloadInfo info, - bool joinOnComplete, - bool isHomeRequested) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing World Download:\n{info.GetLogString()}\n" + - $"JoinOnComplete: {joinOnComplete}\n" + - $"IsHomeRequested: {isHomeRequested}"); - } - - _worldQueue.QueueDownload(in info, joinOnComplete, isHomeRequested); - } - - #endregion Public Queue Methods - - #region Internal Methods - - internal Task ProcessDownload(DownloadInfo info, Action progressCallback = null) - { - return _downloadProcessor.ProcessDownload(info); - } - - #endregion Internal Methods - - #region Private Helper Methods - - internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) - { - CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); - if (player == null) - { - // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); - playerEntity = null; - return false; - } - playerEntity = player; - return true; - } - - internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) - { - CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); - if (prop == null) - { - // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); - propData = null; - return false; - } - propData = prop; - return true; - } - - internal static bool IsPlayerLocal(string playerId) - { - return playerId == MetaPort.Instance.ownerId; - } - - internal static bool IsPlayerFriend(string playerId) - { - return Friends.FriendsWith(playerId); - } - - internal bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) - { - if (player.PuppetMaster == null) return false; - return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; - } - - internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) - { - Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); - return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; - } - - #endregion Private Helper Methods -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadInfo.cs b/BetterContentLoading/BetterContentLoading/DownloadInfo.cs deleted file mode 100644 index 038f6bf..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadInfo.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using ABI_RC.Core.Networking.API.Responses; - -namespace NAK.BetterContentLoading; - -public readonly struct DownloadInfo -{ - public readonly string AssetId; - public readonly string AssetUrl; - public readonly string FileId; - public readonly long FileSize; - public readonly string FileKey; - public readonly string FileHash; - public readonly int CompatibilityVersion; - public readonly int EncryptionAlgorithm; - public readonly UgcTagsData TagsData; - - public readonly string DownloadId; - - public DownloadInfo( - string assetId, string assetUrl, string fileId, - long fileSize, string fileKey, string fileHash, - int compatibilityVersion, int encryptionAlgorithm, - UgcTagsData tagsData) - { - AssetId = assetId + "meow"; - AssetUrl = assetUrl; - FileId = fileId; - FileSize = fileSize; - FileKey = fileKey; - FileHash = fileHash; - CompatibilityVersion = compatibilityVersion; - EncryptionAlgorithm = encryptionAlgorithm; - TagsData = tagsData; - - using SHA256 sha = SHA256.Create(); - StringBuilder sb = new(); - sb.Append(assetId) - .Append('|').Append(assetUrl) - .Append('|').Append(fileId) - .Append('|').Append(fileSize) - .Append('|').Append(fileKey) - .Append('|').Append(fileHash) - .Append('|').Append(compatibilityVersion) - .Append('|').Append(encryptionAlgorithm); - - var bytes = Encoding.UTF8.GetBytes(sb.ToString()); - var hash = sha.ComputeHash(bytes); - DownloadId = Convert.ToBase64String(hash); - } - - public string GetLogString() => - $"AssetId: {AssetId}\n" + - $"DownloadId: {DownloadId}\n" + - $"AssetUrl: {AssetUrl}\n" + - $"FileId: {FileId}\n" + - $"FileSize: {FileSize}\n" + - $"FileKey: {FileKey}\n" + - $"FileHash: {FileHash}\n" + - $"CompatibilityVersion: {CompatibilityVersion}\n" + - $"EncryptionAlgorithm: {EncryptionAlgorithm}"; -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs b/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs deleted file mode 100644 index e77acdc..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Net.Http; -using ABI_RC.Core; -using ABI_RC.Core.IO.AssetManagement; - -namespace NAK.BetterContentLoading; - -public class DownloadProcessor -{ - private readonly HttpClient _client = new(); - private readonly SemaphoreSlim _bandwidthSemaphore = new(1); - private long _bytesReadLastSecond; - - private int _maxDownloadBandwidth = 10 * 1024 * 1024; // Default 10MB/s - private const int MinBufferSize = 16384; // 16KB min buffer - private const long ThrottleThreshold = 25 * 1024 * 1024; // 25MB threshold for throttling - private const long KnownSizeDifference = 1000; // API reported size is 1000 bytes larger than actual content - - public int MaxDownloadBandwidth - { - get => _maxDownloadBandwidth; - set => _maxDownloadBandwidth = Math.Max(1024 * 1024, value); - } - - private int ActiveDownloads { get; set; } - private int CurrentBandwidthPerDownload => MaxDownloadBandwidth / Math.Max(1, ActiveDownloads); - - public int GetProgress(string downloadId) => _downloadProgress.GetValueOrDefault(downloadId, 0); - private readonly Dictionary _downloadProgress = new(); - - public async Task ProcessDownload(DownloadInfo downloadInfo) - { - try - { - if (await CacheManager.Instance.IsCachedFileUpToDate( - downloadInfo.AssetId, - downloadInfo.FileId, - downloadInfo.FileHash)) - return true; - - var filePath = CacheManager.Instance.GetCachePath(downloadInfo.AssetId, downloadInfo.FileId); - if (!CVRTools.HasEnoughDiskSpace(filePath, downloadInfo.FileSize)) - { - BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {downloadInfo.AssetId}"); - return false; - } - - CacheManager.Instance.EnsureCacheDirectoryExists(downloadInfo.AssetId); - - bool success = false; - Exception lastException = null; - - for (int attempt = 0; attempt <= 1; attempt++) - { - try - { - if (attempt > 0) - await Task.Delay(1000); - - success = await DownloadWithBandwidthLimit(downloadInfo, filePath); - if (success) break; - } - catch (Exception ex) - { - lastException = ex; - } - } - - if (!success && lastException != null) - BetterContentLoadingMod.Logger.Error($"Failed to download {downloadInfo.AssetId}: {lastException}"); - - _downloadProgress.Remove(downloadInfo.DownloadId); - return success; - } - catch (Exception ex) - { - BetterContentLoadingMod.Logger.Error($"Error processing download for {downloadInfo.AssetId}: {ex}"); - _downloadProgress.Remove(downloadInfo.DownloadId); - return false; - } - } - - private async Task DownloadWithBandwidthLimit(DownloadInfo downloadInfo, string filePath) - { - await _bandwidthSemaphore.WaitAsync(); - try { ActiveDownloads++; } - finally { _bandwidthSemaphore.Release(); } - - string tempFilePath = filePath + ".download"; - try - { - using var response = await _client.GetAsync(downloadInfo.AssetUrl, HttpCompletionOption.ResponseHeadersRead); - if (!response.IsSuccessStatusCode) - return false; - - var expectedContentSize = downloadInfo.FileSize - KnownSizeDifference; - using var stream = await response.Content.ReadAsStreamAsync(); - using var fileStream = File.Open(tempFilePath, FileMode.Create); - - var isEligibleForThrottle = downloadInfo.FileSize >= ThrottleThreshold; - var totalBytesRead = 0L; - byte[] buffer = null; - - while (true) - { - var lengthToRead = isEligibleForThrottle ? MinBufferSize : CurrentBandwidthPerDownload; - if (buffer == null || lengthToRead != buffer.Length) - buffer = new byte[lengthToRead]; - - var bytesRead = await stream.ReadAsync(buffer, 0, lengthToRead); - if (bytesRead == 0) break; - - if (isEligibleForThrottle) - Interlocked.Add(ref _bytesReadLastSecond, bytesRead); - - fileStream.Write(buffer, 0, bytesRead); - totalBytesRead += bytesRead; - - var progress = (int)(((float)totalBytesRead / expectedContentSize) * 100f); - _downloadProgress[downloadInfo.DownloadId] = Math.Clamp(progress, 0, 100); - } - - fileStream.Flush(); - - var fileInfo = new FileInfo(tempFilePath); - if (fileInfo.Length != expectedContentSize) - return false; - - if (File.Exists(filePath)) - File.Delete(filePath); - File.Move(tempFilePath, filePath); - - CacheManager.Instance.QueuePrune(); - return true; - } - finally - { - await _bandwidthSemaphore.WaitAsync(); - try { ActiveDownloads--; } - finally { _bandwidthSemaphore.Release(); } - - try - { - if (File.Exists(tempFilePath)) - File.Delete(tempFilePath); - } - catch - { - // Ignore cleanup errors - } - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs deleted file mode 100644 index 60060c5..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace NAK.BetterContentLoading.Queue; - -public class AvatarDownloadQueue : ContentDownloadQueueBase -{ - public AvatarDownloadQueue(BetterDownloadManager manager) : base(manager, 3) { } - - public void QueueDownload(in DownloadInfo info, string playerId, Action onComplete = null) - { - float priority = CalculateAvatarPriority(in info, playerId); - QueueDownload(in info, playerId, priority, onComplete); - } - - protected override async Task ProcessDownload(DownloadInfo info) - { - await base.ProcessDownload(info); - } - - protected override void OnDownloadProgress(string downloadId, float progress) - { - if (DownloadOwners.TryGetValue(downloadId, out var owners)) - { - foreach (var playerId in owners) - { - if (BetterDownloadManager.IsPlayerLocal(playerId)) - { - // Update loading progress on local player - BetterContentLoadingMod.Logger.Msg($"Progress for local player ({playerId}): {progress:P}"); - continue; - } - if (BetterDownloadManager.TryGetPlayerEntity(playerId, out var player)) - { - // Update loading progress on avatar controller if needed - BetterContentLoadingMod.Logger.Msg($"Progress for {player.Username} ({playerId}): {progress:P}"); - } - } - } - } - - protected override float RecalculatePriority(string downloadId) - { - if (!DownloadOwners.TryGetValue(downloadId, out var owners)) - return float.MaxValue; - - float lowestPriority = float.MaxValue; - DownloadInfo? downloadInfo = null; - - // Find the queue item to get the DownloadInfo - var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); - if (queueItem.Info.AssetId != null) - downloadInfo = queueItem.Info; - - if (downloadInfo == null) - return lowestPriority; - - // Calculate priority for each owner and use the lowest (highest priority) value - foreach (var playerId in owners) - { - var priority = CalculateAvatarPriority(downloadInfo.Value, playerId); - lowestPriority = Math.Min(lowestPriority, priority); - } - - return lowestPriority; - } - - private float CalculateAvatarPriority(in DownloadInfo info, string playerId) - { - float priority = info.FileSize; - - if (BetterDownloadManager.IsPlayerLocal(playerId)) - return 0f; - - if (!BetterDownloadManager.TryGetPlayerEntity(playerId, out var player)) - return priority; - - if (Manager.PrioritizeFriends && BetterDownloadManager.IsPlayerFriend(playerId)) - priority *= 0.5f; - - if (Manager.PrioritizeDistance && Manager.IsPlayerWithinPriorityDistance(player)) - priority *= 0.75f; - - return priority; - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs deleted file mode 100644 index 66e366b..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs +++ /dev/null @@ -1,177 +0,0 @@ -namespace NAK.BetterContentLoading.Queue; - -public abstract class ContentDownloadQueueBase -{ - protected readonly struct QueueItem - { - public readonly DownloadInfo Info; - public readonly float Priority; - public readonly Action OnComplete; // Callback with (downloadId, ownerId) - - public QueueItem(DownloadInfo info, float priority, Action onComplete) - { - Info = info; - Priority = priority; - OnComplete = onComplete; - } - } - - protected readonly List Queue = new(); - private readonly HashSet ActiveDownloads = new(); // By DownloadId - protected readonly Dictionary> DownloadOwners = new(); // DownloadId -> Set of OwnerIds - private readonly SemaphoreSlim DownloadSemaphore; - protected readonly BetterDownloadManager Manager; - - protected ContentDownloadQueueBase(BetterDownloadManager manager, int maxParallelDownloads) - { - Manager = manager; - DownloadSemaphore = new SemaphoreSlim(maxParallelDownloads); - } - - protected void QueueDownload(in DownloadInfo info, string ownerId, float priority, Action onComplete) - { - if (Manager.IsDebugEnabled) - BetterContentLoadingMod.Logger.Msg($"Attempting to queue download for {info.AssetId} (DownloadId: {info.DownloadId})"); - - // Add to owners tracking - if (!DownloadOwners.TryGetValue(info.DownloadId, out var owners)) - { - owners = new HashSet(); - DownloadOwners[info.DownloadId] = owners; - } - owners.Add(ownerId); - - // If already downloading, just add the owner and callback - if (ActiveDownloads.Contains(info.DownloadId)) - { - if (Manager.IsDebugEnabled) - BetterContentLoadingMod.Logger.Msg($"Already downloading {info.DownloadId}, added owner {ownerId}"); - return; - } - - DownloadInfo downloadInfo = info; - var existingIndex = Queue.FindIndex(x => x.Info.DownloadId == downloadInfo.DownloadId); - if (existingIndex >= 0) - { - // Update priority if needed based on new owner - var newPriority = RecalculatePriority(info.DownloadId); - Queue[existingIndex] = new QueueItem(info, newPriority, onComplete); - SortQueue(); - return; - } - - Queue.Add(new QueueItem(info, priority, onComplete)); - SortQueue(); - TryStartNextDownload(); - } - - public void RemoveOwner(string downloadId, string ownerId) - { - if (!DownloadOwners.TryGetValue(downloadId, out var owners)) - return; - - owners.Remove(ownerId); - - if (owners.Count == 0) - { - // No more owners, cancel the download - DownloadOwners.Remove(downloadId); - CancelDownload(downloadId); - } - else if (!ActiveDownloads.Contains(downloadId)) - { - // Still has owners and is queued, recalculate priority - var existingIndex = Queue.FindIndex(x => x.Info.DownloadId == downloadId); - if (existingIndex >= 0) - { - var item = Queue[existingIndex]; - var newPriority = RecalculatePriority(downloadId); - Queue[existingIndex] = new QueueItem(item.Info, newPriority, item.OnComplete); - SortQueue(); - } - } - } - - protected virtual async void TryStartNextDownload() - { - try - { - if (Queue.Count == 0) return; - - await DownloadSemaphore.WaitAsync(); - - if (Queue.Count > 0) - { - var item = Queue[0]; - Queue.RemoveAt(0); - - // Double check we still have owners before starting download - if (!DownloadOwners.TryGetValue(item.Info.DownloadId, out var owners) || owners.Count == 0) - { - DownloadSemaphore.Release(); - TryStartNextDownload(); - return; - } - - ActiveDownloads.Add(item.Info.DownloadId); - - try - { - await ProcessDownload(item.Info); - BetterContentLoadingMod.Logger.Msg($"Download completed for {item.Info.DownloadId}"); - - // Notify all owners of completion - if (DownloadOwners.TryGetValue(item.Info.DownloadId, out owners)) - { - foreach (var owner in owners) - { - item.OnComplete?.Invoke(item.Info.DownloadId, owner); - } - } - } - catch (Exception ex) - { - if (Manager.IsDebugEnabled) - BetterContentLoadingMod.Logger.Error($"Download failed for {item.Info.DownloadId}: {ex}"); - } - finally - { - ActiveDownloads.Remove(item.Info.DownloadId); - DownloadSemaphore.Release(); - TryStartNextDownload(); - } - } - else - { - DownloadSemaphore.Release(); - } - } - catch (Exception e) - { - BetterContentLoadingMod.Logger.Error($"Error in TryStartNextDownload: {e}"); - } - } - - protected virtual async Task ProcessDownload(DownloadInfo info) - { - bool success = await Manager.ProcessDownload(info); - - if (!success) - throw new Exception($"Failed to download {info.AssetId}"); - } - - protected abstract void OnDownloadProgress(string downloadId, float progress); - - protected abstract float RecalculatePriority(string downloadId); - - protected virtual void SortQueue() - { - Queue.Sort((a, b) => a.Priority.CompareTo(b.Priority)); - } - - protected virtual void CancelDownload(string downloadId) - { - Queue.RemoveAll(x => x.Info.DownloadId == downloadId); - if (ActiveDownloads.Remove(downloadId)) DownloadSemaphore.Release(); - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs deleted file mode 100644 index f100e0b..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs +++ /dev/null @@ -1,81 +0,0 @@ -using ABI_RC.Core.Util; - -namespace NAK.BetterContentLoading.Queue; - -public class PropDownloadQueue : ContentDownloadQueueBase -{ - private readonly Dictionary _ownerToSpawner = new(); // InstanceId -> SpawnerId - - public PropDownloadQueue(BetterDownloadManager manager) : base(manager, 2) { } - - public void QueueDownload(in DownloadInfo info, string instanceId, string spawnerId, Action onComplete = null) - { - _ownerToSpawner[instanceId] = spawnerId; - float priority = CalculatePropPriority(in info, instanceId, spawnerId); - QueueDownload(in info, instanceId, priority, onComplete); - } - - protected override async Task ProcessDownload(DownloadInfo info) - { - await base.ProcessDownload(info); - } - - protected override void OnDownloadProgress(string downloadId, float progress) - { - if (DownloadOwners.TryGetValue(downloadId, out var owners)) - { - foreach (var instanceId in owners) - { - if (BetterDownloadManager.TryGetPropData(instanceId, out CVRSyncHelper.PropData prop)) - { - BetterContentLoadingMod.Logger.Msg($"Progress for {prop.InstanceId}: {progress:P}"); - } - } - } - } - - protected override float RecalculatePriority(string downloadId) - { - if (!DownloadOwners.TryGetValue(downloadId, out var owners)) - return float.MaxValue; - - float lowestPriority = float.MaxValue; - DownloadInfo? downloadInfo = null; - - // Find the queue item to get the DownloadInfo - var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); - if (queueItem.Info.AssetId != null) - downloadInfo = queueItem.Info; - - if (downloadInfo == null) - return lowestPriority; - - // Calculate priority for each owner and use the lowest (highest priority) value - foreach (var instanceId in owners) - { - if (_ownerToSpawner.TryGetValue(instanceId, out var spawnerId)) - { - var priority = CalculatePropPriority(downloadInfo.Value, instanceId, spawnerId); - lowestPriority = Math.Min(lowestPriority, priority); - } - } - - return lowestPriority; - } - - private float CalculatePropPriority(in DownloadInfo info, string instanceId, string spawnerId) - { - float priority = info.FileSize; - - if (!BetterDownloadManager.TryGetPropData(instanceId, out var prop)) - return priority; - - if (Manager.PrioritizeFriends && BetterDownloadManager.IsPlayerFriend(spawnerId)) - priority *= 0.5f; - - if (Manager.PrioritizeDistance && Manager.IsPropWithinPriorityDistance(prop)) - priority *= 0.75f; - - return priority; - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs deleted file mode 100644 index 1cea689..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace NAK.BetterContentLoading.Queue; - -public class WorldDownloadQueue : ContentDownloadQueueBase -{ - private readonly Queue<(DownloadInfo Info, bool JoinOnComplete, bool IsHomeRequest)> _backgroundQueue = new(); - private bool _isProcessingPriorityDownload; - - public WorldDownloadQueue(BetterDownloadManager manager) : base(manager, 1) { } - - public void QueueDownload(in DownloadInfo info, bool joinOnComplete, bool isHomeRequest, Action onComplete = null) - { - if (joinOnComplete || isHomeRequest) - { - // Priority download - clear queue and download immediately - Queue.Clear(); - _isProcessingPriorityDownload = true; - QueueDownload(in info, info.DownloadId, 0f, onComplete); - } - else - { - // Background download - add to background queue - _backgroundQueue.Enqueue((info, false, false)); - if (!_isProcessingPriorityDownload) - ProcessBackgroundQueue(); - } - } - - protected override async Task ProcessDownload(DownloadInfo info) - { - await base.ProcessDownload(info); - - if (_isProcessingPriorityDownload) - { - _isProcessingPriorityDownload = false; - ProcessBackgroundQueue(); - } - } - - protected override void OnDownloadProgress(string downloadId, float progress) - { - BetterContentLoadingMod.Logger.Msg($"World download progress: {progress:P}"); - } - - protected override float RecalculatePriority(string downloadId) - { - // For worlds, priority is based on whether it's a priority download and file size - var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); - return queueItem.Priority; - } - - private void ProcessBackgroundQueue() - { - while (_backgroundQueue.Count > 0) - { - (DownloadInfo info, var join, var home) = _backgroundQueue.Dequeue(); - QueueDownload(in info, info.DownloadId, info.FileSize, null); - } - } - - protected override void CancelDownload(string downloadId) - { - base.CancelDownload(downloadId); - _backgroundQueue.Clear(); - _isProcessingPriorityDownload = false; - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadState.cs b/BetterContentLoading/BetterContentLoading/DownloadState.cs deleted file mode 100644 index 785396b..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadState.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NAK.BetterContentLoading; - -public readonly struct DownloadState -{ - public readonly string DownloadId; - public readonly long BytesRead; - public readonly long TotalBytes; - public readonly int ProgressPercent; - public readonly float BytesPerSecond; - - public DownloadState(string downloadId, long bytesRead, long totalBytes, float bytesPerSecond) - { - DownloadId = downloadId; - BytesRead = bytesRead; - TotalBytes = totalBytes; - BytesPerSecond = bytesPerSecond; - ProgressPercent = (int)(((float)bytesRead / totalBytes) * 100f); - } -} - -public interface IDownloadMonitor -{ - void OnDownloadProgress(DownloadState state); - void OnDownloadStarted(string downloadId); - void OnDownloadCompleted(string downloadId, bool success); -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs b/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs deleted file mode 100644 index 280aeb2..0000000 --- a/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs +++ /dev/null @@ -1,109 +0,0 @@ -using JetBrains.Annotations; -using UnityEngine; - -namespace NAK.BetterContentLoading.Util; - -[PublicAPI] -public static class ThreadingHelper -{ - private static readonly SynchronizationContext _mainThreadContext; - - static ThreadingHelper() - { - _mainThreadContext = SynchronizationContext.Current; - } - - public static bool IsMainThread => SynchronizationContext.Current == _mainThreadContext; - - /// - /// Runs an action on the main thread. Optionally waits for its completion. - /// - public static void RunOnMainThread(Action action, bool waitForCompletion = true, Action onError = null) - { - if (SynchronizationContext.Current == _mainThreadContext) - { - // Already on the main thread - TryExecute(action, onError); - } - else - { - if (waitForCompletion) - { - ManualResetEvent done = new(false); - _mainThreadContext.Post(_ => - { - TryExecute(action, onError); - done.Set(); - }, null); - done.WaitOne(50000); // Block until action is completed - } - else - { - // Fire and forget (don't wait for the action to complete) - _mainThreadContext.Post(_ => TryExecute(action, onError), null); - } - } - } - - /// - /// Runs an action asynchronously on the main thread and returns a Task. - /// - public static Task RunOnMainThreadAsync(Action action, Action onError = null) - { - if (SynchronizationContext.Current == _mainThreadContext) - { - // Already on the main thread - TryExecute(action, onError); - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(); - _mainThreadContext.Post(_ => - { - try - { - TryExecute(action, onError); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }, null); - return tcs.Task; - } - - /// - /// Runs a task on a background thread with cancellation support. - /// - public static async Task RunOffMainThreadAsync(Action action, CancellationToken cancellationToken, Action onError = null) - { - try - { - await Task.Run(() => - { - cancellationToken.ThrowIfCancellationRequested(); - TryExecute(action, onError); - }, cancellationToken); - } - catch (OperationCanceledException) - { - Debug.LogWarning("Task was canceled."); - } - } - - /// - /// Helper method for error handling. - /// - private static void TryExecute(Action action, Action onError) - { - try - { - action(); - } - catch (Exception ex) - { - onError?.Invoke(ex); - } - } -} diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Core.cs b/BetterContentLoading/DownloadManager/DownloadManager.Core.cs deleted file mode 100644 index ce484cd..0000000 --- a/BetterContentLoading/DownloadManager/DownloadManager.Core.cs +++ /dev/null @@ -1,237 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Networking.API.Responses; -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Savior; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager -{ - #region Singleton - private static DownloadManager _instance; - public static DownloadManager Instance => _instance ??= new DownloadManager(); - #endregion - - private DownloadManager() - { - - } - - #region Settings - public bool IsDebugEnabled { get; set; } = true; - public bool PrioritizeFriends { get; set; } = true; - public bool PrioritizeDistance { get; set; } = true; - public float PriorityDownloadDistance { get; set; } = 25f; - public int MaxConcurrentDownloads { get; set; } = 3; - public int MaxDownloadBandwidth { get; set; } = 100 * 1024 * 1024; // 100MB default - private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling - - public long MaxAvatarDownloadSize { get; set; } = 25 * 1024 * 1024; // 25MB default - public long MaxPropDownloadSize { get; set; } = 25 * 1024 * 1024; // 25MB default - - #endregion Settings - - #region State - - // priority -> downloadtask - private readonly SortedList _downloadQueue = new(); - - // downloadId -> downloadtask - private readonly Dictionary _cachedDownloads = new(); - - #endregion State - - #region Public Queue Methods - - public void QueueAvatarDownload( - in DownloadInfo info, - string playerId, - CVRLoadingAvatarController loadingController) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Avatar Download:\n{info.GetLogString()}\n" + - $"PlayerId: {playerId}\n" + - $"LoadingController: {loadingController}"); - } - - if (!ShouldQueueAvatarDownload(info, playerId)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download not eligible: {info.DownloadId}"); - return; - } - - if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download already queued: {info.DownloadId}"); - cachedDownload.AddInstantiationTarget(playerId); - cachedDownload.BasePriority = CalculatePriority(cachedDownload); - return; - } - - DownloadTask task = new() - { - Info = info, - Type = DownloadTaskType.Avatar - }; - task.AddInstantiationTarget(playerId); - task.BasePriority = CalculatePriority(task); - } - - private bool ShouldQueueAvatarDownload( - in DownloadInfo info, - string playerId) - { - // Check if content is incompatible or banned - if (info.TagsData.Incompatible || info.TagsData.AdminBanned) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar is incompatible or banned"); - return false; - } - - // Check if player is blocked - if (MetaPort.Instance.blockedUserIds.Contains(playerId)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Player is blocked: {playerId}"); - return false; - } - - // Check if mature content is disabled - UgcTagsData tags = info.TagsData; - if (!MetaPort.Instance.matureContentAllowed && (tags.Gore || tags.Nudity)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Mature content is disabled"); - return false; - } - - // Check file size - if (info.FileSize > MaxAvatarDownloadSize) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download too large: {info.FileSize} > {MaxAvatarDownloadSize}"); - return false; - } - - // Get visibility status for the avatar - // ForceHidden means player avatar or avatar itself is forced off. - // ForceShown will bypass all checks and return true. - MetaPort.Instance.SelfModerationManager.GetAvatarVisibility(playerId, info.AssetId, - out bool wasForceHidden, out bool wasForceShown); - - if (!wasForceHidden) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar is not visible either because player avatar or avatar itself is forced off"); - return false; - } - - if (!wasForceShown) - { - // Check content filter settings if not force shown - CVRSettings settings = MetaPort.Instance.settings; - bool isLocalPlayer = playerId == MetaPort.Instance.ownerId; - bool isFriend = Friends.FriendsWith(playerId); - bool CheckFilterSettings(string settingName) - { - int settingVal = settings.GetSettingInt(settingName); - switch (settingVal) - { - // Only Self - case 0 when !isLocalPlayer: - // Only Friends - case 1 when !isFriend: - return false; - } - return true; - } - - if (!CheckFilterSettings("ContentFilterVisibility")) return false; - if (!CheckFilterSettings("ContentFilterNudity")) return false; - if (!CheckFilterSettings("ContentFilterGore")) return false; - if (!CheckFilterSettings("ContentFilterSuggestive")) return false; - if (!CheckFilterSettings("ContentFilterFlashingColors")) return false; - if (!CheckFilterSettings("ContentFilterFlashingLights")) return false; - if (!CheckFilterSettings("ContentFilterScreenEffects")) return false; - if (!CheckFilterSettings("ContentFilterExtremelyBright")) return false; - if (!CheckFilterSettings("ContentFilterViolence")) return false; - if (!CheckFilterSettings("ContentFilterJumpscare")) return false; - if (!CheckFilterSettings("ContentFilterExcessivelyHuge")) return false; - if (!CheckFilterSettings("ContentFilterExcessivelySmall")) return false; - } - - // All eligibility checks passed - return true; - } - - /// - /// Queues a prop download. - /// - /// The download info. - /// The instance ID for the prop. - /// The user who spawned the prop. - public void QueuePropDownload( - in DownloadInfo info, - string instanceId, - string spawnerId) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Prop Download:\n{info.GetLogString()}\n" + - $"InstanceId: {instanceId}\n" + - $"SpawnerId: {spawnerId}"); - } - - if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Prop Download already queued: {info.DownloadId}"); - cachedDownload.AddInstantiationTarget(instanceId); - cachedDownload.BasePriority = CalculatePriority(cachedDownload); - return; - } - - DownloadTask task = new() - { - Info = info, - Type = DownloadTaskType.Prop - }; - task.AddInstantiationTarget(instanceId); - task.BasePriority = CalculatePriority(task); - } - - /// - /// Queues a world download. - /// - /// Download info. - /// Whether to load into this world once downloaded. - /// Whether the home world is requested. - public void QueueWorldDownload( - in DownloadInfo info, - bool joinOnComplete, - bool isHomeRequested) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing World Download:\n{info.GetLogString()}\n" + - $"JoinOnComplete: {joinOnComplete}\n" + - $"IsHomeRequested: {isHomeRequested}"); - } - - if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"World Download already queued: {info.DownloadId}"); - cachedDownload.BasePriority = CalculatePriority(cachedDownload); - return; - } - - DownloadTask task = new() - { - Info = info, - Type = DownloadTaskType.World - }; - task.BasePriority = CalculatePriority(task); - } - - #endregion Public Queue Methods - -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs b/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs deleted file mode 100644 index 3664ed4..0000000 --- a/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs +++ /dev/null @@ -1,58 +0,0 @@ -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using UnityEngine; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager -{ - internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) - { - CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); - if (player == null) - { - // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); - playerEntity = null; - return false; - } - playerEntity = player; - return true; - } - - internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) - { - CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); - if (prop == null) - { - // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); - propData = null; - return false; - } - propData = prop; - return true; - } - - private static bool IsPlayerLocal(string playerId) - { - return playerId == MetaPort.Instance.ownerId; - } - - private static bool IsPlayerFriend(string playerId) - { - return Friends.FriendsWith(playerId); - } - - private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) - { - if (player.PuppetMaster == null) return false; - return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; - } - - internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) - { - Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); - return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs b/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs deleted file mode 100644 index ae28cbc..0000000 --- a/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs +++ /dev/null @@ -1,38 +0,0 @@ -using ABI_RC.Core.Player; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager -{ - private float CalculatePriority(DownloadTask task) - { - return task.Type switch - { - DownloadTaskType.Avatar => CalculateAvatarPriority(task), - // DownloadTaskType.Prop => CalculatePropPriority(task2), - // DownloadTaskType.World => CalculateWorldPriority(task2), - _ => task.Info.FileSize - }; - } - - private float CalculateAvatarPriority(DownloadTask task) - { - float priority = task.Info.FileSize; - - foreach (string target in task.InstantiationTargets) - { - if (IsPlayerLocal(target)) return 0f; - - if (!TryGetPlayerEntity(target, out CVRPlayerEntity player)) - return priority; - - if (PrioritizeFriends && IsPlayerFriend(target)) - priority *= 0.5f; - - if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player)) - priority *= 0.75f; - } - - return priority; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadTask.Main.cs b/BetterContentLoading/DownloadManager/DownloadTask.Main.cs deleted file mode 100644 index 5397cf7..0000000 --- a/BetterContentLoading/DownloadManager/DownloadTask.Main.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NAK.BetterContentLoading; - -public partial class DownloadTask -{ - public DownloadInfo Info { get; set; } - public DownloadTaskStatus Status { get; set; } - public DownloadTaskType Type { get; set; } - - public float BasePriority { get; set; } - public float CurrentPriority => BasePriority * (1 + Progress / 100f); - - public long BytesRead { get; set; } - public int Progress { get; set; } - - /// The avatar/prop instances that wish to utilize this bundle. - public List InstantiationTargets { get; } = new(); - - public void AddInstantiationTarget(string target) - { - if (InstantiationTargets.Contains(target)) - return; - - InstantiationTargets.Add(target); - } - - public void RemoveInstantiationTarget(string target) - { - if (!InstantiationTargets.Contains(target)) - return; - - InstantiationTargets.Remove(target); - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs b/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs deleted file mode 100644 index f697a5e..0000000 --- a/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NAK.BetterContentLoading; - -public partial class DownloadTask -{ - -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs b/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs deleted file mode 100644 index df844d5..0000000 --- a/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NAK.BetterContentLoading; - -public class ConcurrentPriorityQueue where TPriority : IComparable -{ - private readonly object _lock = new(); - private readonly SortedDictionary> _queues = new(); - - public void Enqueue(TElement item, TPriority priority) - { - lock (_lock) - { - if (!_queues.TryGetValue(priority, out var queue)) - { - queue = new Queue(); - _queues[priority] = queue; - } - queue.Enqueue(item); - } - } - - public bool TryDequeue(out TElement item, out TPriority priority) - { - lock (_lock) - { - if (_queues.Count == 0) - { - item = default; - priority = default; - return false; - } - - var firstQueue = _queues.First(); - priority = firstQueue.Key; - var queue = firstQueue.Value; - item = queue.Dequeue(); - - if (queue.Count == 0) - _queues.Remove(priority); - - return true; - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs deleted file mode 100644 index 54d5792..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - private void StartBandwidthMonitor() - { - Task.Run(async () => - { - while (true) - { - await Task.Delay(1000); - Interlocked.Exchange(ref _bytesReadLastSecond, 0); - Interlocked.Exchange(ref _completedDownloads, 0); - } - }); - } - - private int ComputeUsableBandwidthPerDownload() - { - var activeCount = _activeDownloads.Count; - if (activeCount == 0) return MaxDownloadBandwidth; - return MaxDownloadBandwidth / activeCount; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs deleted file mode 100644 index 63fd4c2..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Collections.Concurrent; -using ABI_RC.Core; -using ABI_RC.Core.IO; -using ABI_RC.Core.IO.AssetManagement; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - #region Singleton - private static DownloadManager2 _instance; - public static DownloadManager2 Instance => _instance ??= new DownloadManager2(); - #endregion - - #region Settings - public bool IsDebugEnabled { get; set; } = true; - public bool PrioritizeFriends { get; set; } = true; - public bool PrioritizeDistance { get; set; } = true; - public float PriorityDownloadDistance { get; set; } = 25f; - public int MaxConcurrentDownloads { get; set; } = 5; - public int MaxDownloadBandwidth { get; set; } // 100MB default - private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling - #endregion - - #region State - private readonly ConcurrentDictionary _activeDownloads; - private readonly ConcurrentPriorityQueue _queuedDownloads; - private readonly ConcurrentDictionary _completedDownloads; - private readonly object _downloadLock = new(); - private long _bytesReadLastSecond; - #endregion - - private DownloadManager2() - { - _activeDownloads = new ConcurrentDictionary(); - _queuedDownloads = new ConcurrentPriorityQueue(); - MaxDownloadBandwidth = 100 * 1024 * 1024; - StartBandwidthMonitor(); - } - - public async Task QueueAvatarDownload(DownloadInfo info, string playerId) - { - if (await ValidateAndCheckCache(info)) - return true; - - DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Avatar); - task2.AddTarget(playerId); - QueueDownload(task2); - return false; - } - - public async Task QueuePropDownload(DownloadInfo info, string instanceId, string spawnerId) - { - if (await ValidateAndCheckCache(info)) - return true; - - DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Prop); - task2.AddTarget(instanceId, spawnerId); - QueueDownload(task2); - return false; - } - - public async Task QueueWorldDownload(DownloadInfo info, bool loadOnComplete) - { - if (await ValidateAndCheckCache(info)) - return true; - - DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.World, loadOnComplete); - QueueDownload(task2); - return false; - } - - private async Task ValidateAndCheckCache(DownloadInfo info) - { - // Check if already cached and up to date - if (await CacheManager.Instance.IsCachedFileUpToDate( - info.AssetId, - info.FileId, - info.FileHash)) - { - return true; - } - - // Validate disk space - var filePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId); - if (!CVRTools.HasEnoughDiskSpace(filePath, info.FileSize)) - { - BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {info.AssetId}"); - return false; - } - - // Ensure cache directory exists - CacheManager.Instance.EnsureCacheDirectoryExists(info.AssetId); - return false; - } - - private DownloadTask2 GetOrCreateDownloadTask(DownloadInfo info, DownloadTaskType type, bool loadOnComplete = false) - { - // Check if task already exists in active downloads - if (_activeDownloads.TryGetValue(info.DownloadId, out var activeTask)) - return activeTask; - - // Check if task exists in queued downloads - var queuedTask = _queuedDownloads.TryFind(t => t.Info.DownloadId == info.DownloadId); - if (queuedTask != null) - return queuedTask; - - // Create new task - var cachePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId); - return new DownloadTask2(info, cachePath, type, loadOnComplete); - } - - public bool TryFindTask(string downloadId, out DownloadTask2 task2) - { - return _activeDownloads.TryGetValue(downloadId, out task2) || - _completedDownloads.TryGetValue(downloadId, out task2) || - TryFindQueuedTask(downloadId, out task2); - } - - private bool TryFindQueuedTask(string downloadId, out DownloadTask2 task2) - { - task2 = _queuedDownloads.UnorderedItems - .FirstOrDefault(x => x.Element.Info.DownloadId == downloadId).Element; - return task2 != null; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs deleted file mode 100644 index 2dd205d..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs +++ /dev/null @@ -1,58 +0,0 @@ -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using UnityEngine; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) - { - CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); - if (player == null) - { - // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); - playerEntity = null; - return false; - } - playerEntity = player; - return true; - } - - internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) - { - CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); - if (prop == null) - { - // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); - propData = null; - return false; - } - propData = prop; - return true; - } - - private static bool IsPlayerLocal(string playerId) - { - return playerId == MetaPort.Instance.ownerId; - } - - private static bool IsPlayerFriend(string playerId) - { - return Friends.FriendsWith(playerId); - } - - private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) - { - if (player.PuppetMaster == null) return false; - return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; - } - - internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) - { - Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); - return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs deleted file mode 100644 index 1132b8e..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs +++ /dev/null @@ -1,47 +0,0 @@ -using ABI_RC.Core.IO; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - private float CalculatePriority(DownloadTask2 task2) - { - return task2.Type switch - { - DownloadTaskType.Avatar => CalculateAvatarPriority(task2), - DownloadTaskType.Prop => CalculatePropPriority(task2), - DownloadTaskType.World => CalculateWorldPriority(task2), - _ => task2.Info.FileSize - }; - } - - private float CalculateAvatarPriority(DownloadTask2 task2) - { - float priority = task2.Info.FileSize; - - if (IsPlayerLocal(task2.PlayerId)) - return 0f; - - if (!TryGetPlayerEntity(task2.PlayerId, out var player)) - return priority; - - if (PrioritizeFriends && IsPlayerFriend(task2.PlayerId)) - priority *= 0.5f; - - if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player)) - priority *= 0.75f; - - // Factor in download progress - priority *= (1 + task2.Progress / 100f); - - return priority; - } - - private float CalculatePropPriority(DownloadTask2 task2) - { - float priority = task2.Info.FileSize; - - if (IsPlayerLocal(task2.PlayerId)) - return 0f; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs deleted file mode 100644 index 698ac06..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Diagnostics; -using System.Net.Http.Headers; -using ABI_RC.Core.IO; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - private async Task ProcessDownload(DownloadTask2 task2) - { - using var client = new HttpClient(); - - // Set up resume headers if we have a resume token - if (!string.IsNullOrEmpty(task2.ResumeToken)) - { - client.DefaultRequestHeaders.Range = new RangeHeaderValue(task2.BytesRead, null); - } - - using var response = await client.GetAsync(task2.Info.AssetUrl, HttpCompletionOption.ResponseHeadersRead); - using var dataStream = await response.Content.ReadAsStreamAsync(); - - // Open file in append mode if resuming, otherwise create new - using var fileStream = new FileStream( - task2.TargetPath, - string.IsNullOrEmpty(task2.ResumeToken) ? FileMode.Create : FileMode.Append, - FileAccess.Write); - - bool isEligibleForThrottle = task2.Info.FileSize > THROTTLE_THRESHOLD; - var stopwatch = new Stopwatch(); - - int bytesRead; - do - { - if (task2.Status == DownloadTaskStatus.Paused) - { - task2.ResumeToken = GenerateResumeToken(task2); - await fileStream.FlushAsync(); - return; - } - - if (task2.Status != DownloadTaskStatus.Downloading) - { - HandleCancellation(task2, dataStream, fileStream); - return; - } - - stopwatch.Restart(); - var lengthToRead = isEligibleForThrottle ? - ComputeUsableBandwidthPerDownload() : - 16384; - - var buffer = new byte[lengthToRead]; - bytesRead = await dataStream.ReadAsync(buffer, 0, lengthToRead); - - if (isEligibleForThrottle) - Interlocked.Add(ref _bytesReadLastSecond, bytesRead); - - await fileStream.WriteAsync(buffer, 0, bytesRead); - UpdateProgress(task2, bytesRead); - - } while (bytesRead > 0); - - CompleteDownload(task2); - } - - private string GenerateResumeToken(DownloadTask2 task2) - { - // Generate a unique token that includes file position and hash - return $"{task2.BytesRead}:{DateTime.UtcNow.Ticks}"; - } - - private async Task FinalizeDownload(DownloadTask2 task2) - { - var tempPath = task2.CachePath + ".tmp"; - - try - { - // Decrypt the file if needed - if (task2.Info.EncryptionAlgorithm != 0) - { - await DecryptFile(tempPath, task2.CachePath, task2.Info); - File.Delete(tempPath); - } - else - { - File.Move(tempPath, task2.CachePath); - } - - CompleteDownload(task2); - } - catch (Exception ex) - { - // _logger.Error($"Failed to finalize download for {task.Info.AssetId}: {ex.Message}"); - task2.Status = DownloadTaskStatus.Failed; - File.Delete(tempPath); - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - } - } - - private async Task DecryptFile(string sourcePath, string targetPath, DownloadInfo info) - { - // Implementation of file decryption based on EncryptionAlgorithm - // This would use the FileKey from the DownloadInfo - throw new NotImplementedException("File decryption not implemented"); - } - - private void HandleCancellation(DownloadTask2 task2, Stream dataStream, Stream fileStream) - { - if (task2.Status != DownloadTaskStatus.Failed) - task2.Status = DownloadTaskStatus.Cancelled; - - dataStream.Close(); - fileStream.Close(); - - task2.Progress = 0; - task2.BytesRead = 0; - - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - } - - private void UpdateProgress(DownloadTask2 task2, int bytesRead) - { - task2.BytesRead += bytesRead; - task2.Progress = Math.Clamp( - (int)(((float)task2.BytesRead / task2.Info.FileSize) * 100f), - 0, 100); - } - - private void CompleteDownload(DownloadTask2 task2) - { - task2.Status = DownloadTaskStatus.Complete; - task2.Progress = 100; - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - _completedDownloads.TryAdd(task2.Info.DownloadId, task2); - - lock (_downloadLock) - { - if (_queuedDownloads.TryDequeue(out var nextTask, out _)) - { - StartDownload(nextTask); - } - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs deleted file mode 100644 index e8a580a..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs +++ /dev/null @@ -1,80 +0,0 @@ -using ABI_RC.Core.IO; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - public void QueueDownload(DownloadTask2 newTask2) - { - if (_completedDownloads.TryGetValue(newTask2.Info.DownloadId, out var completedTask)) - { - completedTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault()); - return; - } - - if (TryFindTask(newTask2.Info.DownloadId, out var existingTask)) - { - existingTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault()); - RecalculatePriority(existingTask); - return; - } - - float priority = CalculatePriority(newTask2); - newTask2.CurrentPriority = priority; - - lock (_downloadLock) - { - if (_activeDownloads.Count < MaxConcurrentDownloads) - { - StartDownload(newTask2); - return; - } - - var lowestPriorityTask = _activeDownloads.Values - .OrderByDescending(t => t.CurrentPriority * (1 + t.Progress / 100f)) - .LastOrDefault(); - - if (lowestPriorityTask != null && priority < lowestPriorityTask.CurrentPriority) - { - PauseDownload(lowestPriorityTask); - StartDownload(newTask2); - } - else - { - _queuedDownloads.Enqueue(newTask2, priority); - } - } - } - - private void StartDownload(DownloadTask2 task2) - { - task2.Status = DownloadTaskStatus.Downloading; - _activeDownloads.TryAdd(task2.Info.DownloadId, task2); - Task.Run(() => ProcessDownload(task2)); - } - - private void PauseDownload(DownloadTask2 task2) - { - task2.Status = DownloadTaskStatus.Queued; - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - _queuedDownloads.Enqueue(task2, task2.CurrentPriority); - } - - - - - private void RecalculatePriority(DownloadTask2 task2) - { - var newPriority = CalculatePriority(task2); - if (Math.Abs(newPriority - task2.CurrentPriority) < float.Epsilon) - return; - - task2.CurrentPriority = newPriority; - - if (task2.Status == DownloadTaskStatus.Queued || task2.Status == DownloadTaskStatus.Paused) - { - // Re-enqueue with new priority - _queuedDownloads.UpdatePriority(task2, newPriority); - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadTask2.cs b/BetterContentLoading/DownloadManager2/DownloadTask2.cs deleted file mode 100644 index 0c531f6..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadTask2.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NAK.BetterContentLoading; - -public class DownloadTask2 -{ - public DownloadInfo Info { get; } - public DownloadTaskStatus Status { get; set; } - public DownloadTaskType Type { get; } - - public float BasePriority { get; set; } - public float CurrentPriority => BasePriority * (1 + Progress / 100f); - - public long BytesRead { get; set; } - public int Progress { get; set; } - - public string CachePath { get; } - public Dictionary Targets { get; } // Key: targetId (playerId/instanceId), Value: spawnerId - public bool LoadOnComplete { get; } // For worlds only - - public DownloadTask2( - DownloadInfo info, - string cachePath, - DownloadTaskType type, - bool loadOnComplete = false) - { - Info = info; - CachePath = cachePath; - Type = type; - LoadOnComplete = loadOnComplete; - Targets = new Dictionary(); - Status = DownloadTaskStatus.Queued; - } - - public void AddTarget(string targetId, string spawnerId = null) - { - if (Type == DownloadTaskType.World && Targets.Count > 0) - throw new InvalidOperationException("World downloads cannot have multiple targets"); - - Targets[targetId] = spawnerId; - } - - public void RemoveTarget(string targetId) - { - Targets.Remove(targetId); - } -} - -public enum DownloadTaskType -{ - Avatar, - Prop, - World -} - -public enum DownloadTaskStatus -{ - Queued, - Downloading, - Paused, - Complete, - Failed, - Cancelled -} \ No newline at end of file diff --git a/BetterContentLoading/Main.cs b/BetterContentLoading/Main.cs deleted file mode 100644 index 5b5d812..0000000 --- a/BetterContentLoading/Main.cs +++ /dev/null @@ -1,37 +0,0 @@ -using MelonLoader; -using NAK.BetterContentLoading.Patches; - -namespace NAK.BetterContentLoading; - -public class BetterContentLoadingMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - #region Melon Events - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ApplyPatches(typeof(CVRDownloadManager_Patches)); - } - - #endregion Melon Events - - #region Melon Mod Utilities - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - #endregion Melon Mod Utilities -} \ No newline at end of file diff --git a/BetterContentLoading/ModSettings.cs b/BetterContentLoading/ModSettings.cs deleted file mode 100644 index f07436c..0000000 --- a/BetterContentLoading/ModSettings.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MelonLoader; - -namespace NAK.RCCVirtualSteeringWheel; - -internal static class ModSettings -{ - #region Constants - - private const string ModName = nameof(RCCVirtualSteeringWheel); - - #endregion Constants - - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); - - internal static readonly MelonPreferences_Entry EntryOverrideSteeringRange = - Category.CreateEntry("override_steering_range", false, - "Override Steering Range", description: "Should the steering wheel use a custom steering range instead of the vehicle's default?"); - - internal static readonly MelonPreferences_Entry EntryCustomSteeringRange = - Category.CreateEntry("custom_steering_range", 60f, - "Custom Steering Range", description: "The custom steering range in degrees when override is enabled (default: 60)"); - - internal static readonly MelonPreferences_Entry EntryInvertSteering = - Category.CreateEntry("invert_steering", false, - "Invert Steering", description: "Inverts the steering direction"); - - #endregion Melon Preferences -} \ No newline at end of file diff --git a/BetterContentLoading/Patches.cs b/BetterContentLoading/Patches.cs deleted file mode 100644 index d3c452b..0000000 --- a/BetterContentLoading/Patches.cs +++ /dev/null @@ -1,71 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.IO; -using ABI_RC.Core.Networking.API; -using ABI_RC.Core.Networking.API.Responses; -using HarmonyLib; -using NAK.BetterContentLoading.Util; - -namespace NAK.BetterContentLoading.Patches; - -internal static class CVRDownloadManager_Patches -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(CVRDownloadManager), nameof(CVRDownloadManager.QueueTask))] - private static bool Prefix_CVRDownloadManager_QueueTask( - string assetId, - DownloadTask2.ObjectType type, - string assetUrl, - string fileId, - long fileSize, - string fileKey, - string toAttach, - string fileHash = null, - UgcTagsData tagsData = null, - CVRLoadingAvatarController loadingAvatarController = null, - bool joinOnComplete = false, - bool isHomeRequested = false, - int compatibilityVersion = 0, - int encryptionAlgorithm = 0, - string spawnerId = null) - { - DownloadInfo info; - - switch (type) - { - case DownloadTask2.ObjectType.Avatar: - info = new DownloadInfo( - assetId, assetUrl, fileId, fileSize, fileKey, fileHash, - compatibilityVersion, encryptionAlgorithm, tagsData); - BetterDownloadManager.Instance.QueueAvatarDownload(in info, toAttach, loadingAvatarController); - return true; - case DownloadTask2.ObjectType.Prop: - info = new DownloadInfo( - assetId, assetUrl, fileId, fileSize, fileKey, fileHash, - compatibilityVersion, encryptionAlgorithm, tagsData); - BetterDownloadManager.Instance.QueuePropDownload(in info, toAttach, spawnerId); - return true; - case DownloadTask2.ObjectType.World: - _ = ThreadingHelper.RunOffMainThreadAsync(() => - { - var response = ApiConnection.MakeRequest( - ApiConnection.ApiOperation.WorldMeta, - new { worldID = assetId } - ); - - if (response?.Result.Data == null) - return; - - info = new DownloadInfo( - assetId, response.Result.Data.FileLocation, response.Result.Data.FileId, - response.Result.Data.FileSize, response.Result.Data.FileKey, response.Result.Data.FileHash, - (int)response.Result.Data.CompatibilityVersion, (int)response.Result.Data.EncryptionAlgorithm, - null); - - BetterDownloadManager.Instance.QueueWorldDownload(in info, joinOnComplete, isHomeRequested); - }, CancellationToken.None); - return true; - default: - return true; - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/Properties/AssemblyInfo.cs b/BetterContentLoading/Properties/AssemblyInfo.cs deleted file mode 100644 index 9ab241d..0000000 --- a/BetterContentLoading/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using MelonLoader; -using NAK.BetterContentLoading; -using NAK.BetterContentLoading.Properties; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.BetterContentLoading))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.BetterContentLoading))] - -[assembly: MelonInfo( - typeof(BetterContentLoadingMod), - nameof(NAK.BetterContentLoading), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BetterContentLoading" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.BetterContentLoading.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/BetterContentLoading/README.md b/BetterContentLoading/README.md deleted file mode 100644 index 8d9f529..0000000 --- a/BetterContentLoading/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# RCCVirtualSteeringWheel - -Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.~~~~ \ No newline at end of file diff --git a/BetterContentLoading/format.json b/BetterContentLoading/format.json deleted file mode 100644 index f565d36..0000000 --- a/BetterContentLoading/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "RCCVirtualSteeringWheel", - "modversion": "1.0.1", - "gameversion": "2024r177", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n", - "searchtags": [ - "rcc", - "steering", - "vehicle", - "car" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/RCCVirtualSteeringWheel.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", - "changelog": "- Initial release", - "embedcolor": "#f61963" -} \ No newline at end of file diff --git a/PlayerColorsAPI/Main.cs b/PlayerColorsAPI/Main.cs deleted file mode 100644 index 9e5634d..0000000 --- a/PlayerColorsAPI/Main.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using MelonLoader; - -namespace NAK.VisualCloneFix; - -public class VisualCloneFixMod : MelonMod -{ - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(VisualCloneFix)); - - internal static readonly MelonPreferences_Entry EntryUseVisualClone = - Category.CreateEntry("use_visual_clone", true, - "Use Visual Clone", description: "Uses the potentially faster Visual Clone setup for the local avatar."); - - #endregion Melon Preferences - - #region Melon Events - - public override void OnInitializeMelon() - { - ApplyPatches(typeof(Patches)); // slapped together a fix cause HarmonyInstance.Patch was null ref for no reason? - } - - #endregion Melon Events - - #region Melon Mod Utilities - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - #endregion Melon Mod Utilities -} \ No newline at end of file diff --git a/PlayerColorsAPI/Patches.cs b/PlayerColorsAPI/Patches.cs deleted file mode 100644 index 2fc9a14..0000000 --- a/PlayerColorsAPI/Patches.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using ABI_RC.Core.Player.LocalClone; -using ABI_RC.Core.Player.TransformHider; -using ABI.CCK.Components; -using HarmonyLib; -using UnityEngine; -using Debug = UnityEngine.Debug; - -namespace NAK.VisualCloneFix; - -public static class Patches -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))] - private static bool OnSetupAvatar(GameObject avatar) - { - if (!VisualCloneFixMod.EntryUseVisualClone.Value) - return true; - - LocalCloneHelper.SetupAvatar(avatar); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(LocalCloneHelper), nameof(LocalCloneHelper.CollectTransformToExclusionMap))] - private static bool CollectTransformToExclusionMap( - Component root, Transform headBone, - ref Dictionary __result) - { - // add an fpr exclusion to the head bone - if (!headBone.TryGetComponent(out FPRExclusion headExclusion)) - { - headExclusion = headBone.gameObject.AddComponent(); - headExclusion.isShown = false; // default to hidden - headExclusion.target = headBone; - } - - MeshHiderExclusion headExclusionBehaviour = new(); - headExclusion.behaviour = headExclusionBehaviour; - headExclusionBehaviour.id = 1; // head bone is always 1 - - // get all FPRExclusions - var fprExclusions = root.GetComponentsInChildren(true); - - // get all valid exclusion targets, and destroy invalid exclusions - Dictionary exclusionTargets = new(); - - int nextId = 2; - foreach (FPRExclusion exclusion in fprExclusions) - { - if (exclusion.target == null - || exclusionTargets.ContainsKey(exclusion.target) - || !exclusion.target.gameObject.scene.IsValid()) - continue; // invalid exclusion - - if (exclusion.behaviour == null) // head exclusion is already created - { - MeshHiderExclusion meshHiderExclusion = new(); - exclusion.behaviour = meshHiderExclusion; - meshHiderExclusion.id = nextId++; - } - - // first to add wins - exclusionTargets.TryAdd(exclusion.target, exclusion); - } - - // process each FPRExclusion (recursive) - int exclusionCount = exclusionTargets.Values.Count; - for (var index = 0; index < exclusionCount; index++) - { - FPRExclusion exclusion = exclusionTargets.Values.ElementAt(index); - ProcessExclusion(exclusion, exclusion.target); - exclusion.UpdateExclusions(); // initial state - } - - __result = exclusionTargets; - return false; - - void ProcessExclusion(FPRExclusion exclusion, Transform transform) - { - if (exclusionTargets.ContainsKey(transform) - && exclusionTargets[transform] != exclusion) return; // found other exclusion root - - exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) - foreach (Transform child in transform) - ProcessExclusion(exclusion, child); // process children - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(SkinnedLocalClone), nameof(SkinnedLocalClone.FindExclusionVertList))] - private static bool FindExclusionVertList( - SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions, - ref int[] __result) - { - // Start the stopwatch - Stopwatch stopwatch = new(); - stopwatch.Start(); - - var boneWeights = renderer.sharedMesh.boneWeights; - var bones = renderer.bones; - int boneCount = bones.Length; - - bool[] boneHasExclusion = new bool[boneCount]; - - // Populate the weights array - for (int i = 0; i < boneCount; i++) - { - Transform bone = bones[i]; - if (bone == null) continue; - if (exclusions.ContainsKey(bone)) - boneHasExclusion[i] = true; - } - - const float minWeightThreshold = 0.2f; - - int[] vertexIndices = new int[renderer.sharedMesh.vertexCount]; - - // Check bone weights and add vertex to exclusion list if needed - for (int i = 0; i < boneWeights.Length; i++) - { - BoneWeight weight = boneWeights[i]; - Transform bone; - - if (boneHasExclusion[weight.boneIndex0] && weight.weight0 > minWeightThreshold) - bone = bones[weight.boneIndex0]; - else if (boneHasExclusion[weight.boneIndex1] && weight.weight1 > minWeightThreshold) - bone = bones[weight.boneIndex1]; - else if (boneHasExclusion[weight.boneIndex2] && weight.weight2 > minWeightThreshold) - bone = bones[weight.boneIndex2]; - else if (boneHasExclusion[weight.boneIndex3] && weight.weight3 > minWeightThreshold) - bone = bones[weight.boneIndex3]; - else continue; - - if (exclusions.TryGetValue(bone, out FPRExclusion exclusion)) - vertexIndices[i] = ((MeshHiderExclusion)(exclusion.behaviour)).id; - } - - // Stop the stopwatch - stopwatch.Stop(); - - // Log the execution time - Debug.Log($"FindExclusionVertList execution time: {stopwatch.ElapsedMilliseconds} ms"); - - __result = vertexIndices; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MeshHiderExclusion), nameof(MeshHiderExclusion.UpdateExclusions))] - private static bool OnUpdateExclusions(bool isShown, bool shrinkToZero, ref int ___id) - { - if (isShown) LocalCloneManager.cullingMask &= ~(1 << ___id); - else LocalCloneManager.cullingMask |= 1 << ___id; - return false; - } -} \ No newline at end of file diff --git a/PlayerColorsAPI/PlayerColorsAPI.csproj b/PlayerColorsAPI/PlayerColorsAPI.csproj deleted file mode 100644 index 8dc4bcd..0000000 --- a/PlayerColorsAPI/PlayerColorsAPI.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - LocalCloneFix - - diff --git a/PlayerColorsAPI/Properties/AssemblyInfo.cs b/PlayerColorsAPI/Properties/AssemblyInfo.cs deleted file mode 100644 index 10b75bb..0000000 --- a/PlayerColorsAPI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MelonLoader; -using NAK.VisualCloneFix.Properties; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.VisualCloneFix))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.VisualCloneFix))] - -[assembly: MelonInfo( - typeof(NAK.VisualCloneFix.VisualCloneFixMod), - nameof(NAK.VisualCloneFix), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.VisualCloneFix.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.1"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/PlayerColorsAPI/README.md b/PlayerColorsAPI/README.md deleted file mode 100644 index cc12a9c..0000000 --- a/PlayerColorsAPI/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# VisualCloneFix - -Fixes the Visual Clone system and allows you to use it again. - -Using the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load. - -**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/PlayerColorsAPI/format.json b/PlayerColorsAPI/format.json deleted file mode 100644 index 6634e9f..0000000 --- a/PlayerColorsAPI/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 221, - "name": "VisualCloneFix", - "modversion": "1.0.1", - "gameversion": "2024r175", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Fixes the Visual Clone system and allows you to use it again.\n\nUsing the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load.\n\n**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it.", - "searchtags": [ - "visual", - "clone", - "head", - "hiding" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/VisualCloneFix.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix/", - "changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.\n- Sped up FindExclusionVertList by 100x by not being an idiot. This heavily reduces avatar hitch with Visual Clone active.", - "embedcolor": "#f61963" -} \ No newline at end of file