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