diff --git a/AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.csproj b/AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.csproj new file mode 100644 index 0000000..e94f9dc --- /dev/null +++ b/AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.csproj @@ -0,0 +1,2 @@ + + diff --git a/AvatarQueueSystemTweaks/Main.cs b/AvatarQueueSystemTweaks/Main.cs new file mode 100644 index 0000000..403e33b --- /dev/null +++ b/AvatarQueueSystemTweaks/Main.cs @@ -0,0 +1,42 @@ +using MelonLoader; +using NAK.AvatarQueueSystemTweaks.Patches; + +namespace NAK.AvatarQueueSystemTweaks; + +public class AvatarQueueSystemTweaksMod : MelonMod +{ + public static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(AvatarQueueSystemTweaks)); + + public static readonly MelonPreferences_Entry EntryPrioritizeSelf = + Category.CreateEntry("prioritize_self", true, "Prioritize Self", description: "Prioritize loading of your own avatar over others."); + + public static readonly MelonPreferences_Entry EntryPrioritizeFriends = + Category.CreateEntry("prioritize_friends", true, "Prioritize Friends", description: "Prioritize loading of friends avatars over others."); + + public static readonly MelonPreferences_Entry EntryLoadByDistance = + Category.CreateEntry("load_by_distance", true, "Load By Distance", description: "Prioritize loading of avatars by distance."); + + // public static readonly MelonPreferences_Entry EntryChokeInstantiation = + // Category.CreateEntry("choke_instantiation", false, "Choke Instantiation", + // description: "Chokes the instantiation queue by waiting 0.2s"); + + public override void OnInitializeMelon() + { + //ApplyPatches(typeof(CVRObjectLoaderPatches)); + ApplyPatches(typeof(AvatarQueueSystemPatches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } +} \ No newline at end of file diff --git a/AvatarQueueSystemTweaks/Patches.cs b/AvatarQueueSystemTweaks/Patches.cs new file mode 100644 index 0000000..74a86ac --- /dev/null +++ b/AvatarQueueSystemTweaks/Patches.cs @@ -0,0 +1,117 @@ +using System.Collections; +using ABI_RC.Core; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; +using ABI_RC.Core.Util; +using HarmonyLib; +using UnityEngine; + +namespace NAK.AvatarQueueSystemTweaks.Patches; + +// internal static class CVRObjectLoaderPatches +// { +// private static readonly YieldInstruction _yieldInstruction = new WaitForSeconds(0.2f); +// +// [HarmonyPostfix] +// [HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateAvatarFromBundle))] +// [HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateSpawnableFromBundle))] +// //[HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateAvatarFromExistingPrefab))] +// //[HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateSpawnableFromExistingPrefab))] +// private static IEnumerator MyWrapper(IEnumerator result) +// { +// while (result.MoveNext()) +// yield return result.Current; +// +// if (AvatarQueueSystemTweaksMod.EntryChokeInstantiation.Value) +// yield return _yieldInstruction; +// } +// } + +internal static class AvatarQueueSystemPatches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(AvatarQueueSystem.CoroutineHandle), nameof(AvatarQueueSystem.CoroutineHandle.RunManagedCoroutine))] + private static bool Prefix_AvatarQueueSystem_CoroutineHandle_RunManagedCoroutine(AvatarQueueSystem.CoroutineHandle __instance) + { + AvatarQueueSystem.Instance.jobIsActive = true; + + // difference from original: doesn't immediately call CheckAvailability on same frame of completion + + __instance._activeCoroutine = AvatarQueueSystem.Instance.StartCoroutine(CoroutineUtil.RunThrowingIterator(__instance._function, delegate(Exception exception) + { + CommonTools.Log(CommonTools.LogLevelType_t.Error, string.Format("[CVRGame => {0}] Avatar loading job has failed!\n{1}", __instance.GetType().Name, exception), "A0108"); + __instance.CleanCurrentJob(); + var onException = __instance._onException; + onException?.Invoke(exception); + }, delegate + { + CommonTools.Log(CommonTools.LogLevelType_t.Info, "[CVRGame => " + __instance.GetType().Name + "] Avatar loading job has completed successfully.", "A0107"); + __instance.CleanCurrentJob(); + Action onSuccess = __instance._onSuccess; + onSuccess?.Invoke(); + })); + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(AvatarQueueSystem), nameof(AvatarQueueSystem.CheckAvailability))] + private static bool Prefix_AvatarQueueSystem_CheckAvailability(AvatarQueueSystem __instance) + { + if (__instance.JobIsActive + || __instance.activeCoroutines.Count == 0) + return false; + + bool prioritizeSelf = AvatarQueueSystemTweaksMod.EntryPrioritizeSelf.Value; + bool prioritizeFriends = AvatarQueueSystemTweaksMod.EntryPrioritizeFriends.Value; + bool loadByDistance = AvatarQueueSystemTweaksMod.EntryLoadByDistance.Value; + + bool foundFriend = false; + float nearestDistance = float.MaxValue; + AvatarQueueSystem.CoroutineHandle nextCoroutine = null; + Vector3 playerPosition = PlayerSetup.Instance.activeCam.transform.position; + + foreach (AvatarQueueSystem.CoroutineHandle coroutine in __instance.activeCoroutines) + { + if (prioritizeSelf && coroutine.player == "_PLAYERLOCAL") // prioritize local player if setting is enabled + { + nextCoroutine = coroutine; + break; + } + + CVRPlayerManager.Instance.GetPlayerPuppetMaster(coroutine.player, out PuppetMaster puppetMaster); + if (puppetMaster == null) + continue; + + if (prioritizeFriends) + { + switch (foundFriend) + { + // attempt find first friend + case false when Friends.FriendsWith(coroutine.player): + foundFriend = true; + nearestDistance = float.MaxValue; + nextCoroutine = coroutine; + if (!loadByDistance) break; // no reason to continue loop + continue; + // found a friend, filtering will now only respect friends for this iteration + case true when !Friends.FriendsWith(coroutine.player): + continue; + } + } + + // filtering by distance + if (!loadByDistance) continue; + + float distance = Vector3.Distance(playerPosition, puppetMaster.transform.position); + if (!(distance < nearestDistance)) continue; // update nearest coroutine if closer + + nearestDistance = distance; + nextCoroutine = coroutine; + } + + nextCoroutine?.RunManagedCoroutine(); + return false; + } +} \ No newline at end of file diff --git a/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs b/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1273c2c --- /dev/null +++ b/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.AvatarQueueSystemTweaks.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.AvatarQueueSystemTweaks))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.AvatarQueueSystemTweaks))] + +[assembly: MelonInfo( + typeof(NAK.AvatarQueueSystemTweaks.AvatarQueueSystemTweaksMod), + nameof(NAK.AvatarQueueSystemTweaks), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatatQueueSystemTweaks" +)] + +[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.AvatarQueueSystemTweaks.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/AvatarQueueSystemTweaks/README.md b/AvatarQueueSystemTweaks/README.md new file mode 100644 index 0000000..3fdd71a --- /dev/null +++ b/AvatarQueueSystemTweaks/README.md @@ -0,0 +1,18 @@ +# AvatarQueueSystemTweaks + +Small tweaks to the Avatar Queue System. + +- Prioritize loading own avatar over others. +- Prioritize loading friends' avatars over others. +- Load avatars by distance from player. + +--- + +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/AvatarQueueSystemTweaks/format.json b/AvatarQueueSystemTweaks/format.json new file mode 100644 index 0000000..b13f19c --- /dev/null +++ b/AvatarQueueSystemTweaks/format.json @@ -0,0 +1,24 @@ +{ + "_id": -1, + "name": "AvatarQueueSystemTweaks", + "modversion": "1.0.0", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Small tweaks to the Avatar Queue System.\n\n- Prioritize loading own avatar over others.\n- Prioritize loading friends' avatars over others.\n- Load avatars by distance from player.", + "searchtags": [ + "avatar", + "load", + "queue", + "initialize", + "download" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/AvatarQueueSystemTweaks.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarQueueSystemTweaks/", + "changelog": "- Initial Release", + "embedcolor": "#e87d0d" +} \ No newline at end of file