From c13dc8375a917c17a0fd072e14c625e840733191 Mon Sep 17 00:00:00 2001
From: NotAKid <37721153+NotAKidoS@users.noreply.github.com>
Date: Wed, 15 Apr 2026 18:44:53 -0500
Subject: [PATCH 1/2] [ChatBoxHud] Initial release
---
ChatBoxHud/ChatBoxHud.csproj | 6 +
ChatBoxHud/Main.cs | 438 ++++++++++++++++++++++++++
ChatBoxHud/Properties/AssemblyInfo.cs | 32 ++
ChatBoxHud/README.md | 14 +
ChatBoxHud/format.json | 23 ++
5 files changed, 513 insertions(+)
create mode 100644 ChatBoxHud/ChatBoxHud.csproj
create mode 100644 ChatBoxHud/Main.cs
create mode 100644 ChatBoxHud/Properties/AssemblyInfo.cs
create mode 100644 ChatBoxHud/README.md
create mode 100644 ChatBoxHud/format.json
diff --git a/ChatBoxHud/ChatBoxHud.csproj b/ChatBoxHud/ChatBoxHud.csproj
new file mode 100644
index 0000000..1b27897
--- /dev/null
+++ b/ChatBoxHud/ChatBoxHud.csproj
@@ -0,0 +1,6 @@
+
+
+
+ ChatBoxHud
+
+
diff --git a/ChatBoxHud/Main.cs b/ChatBoxHud/Main.cs
new file mode 100644
index 0000000..107e8e6
--- /dev/null
+++ b/ChatBoxHud/Main.cs
@@ -0,0 +1,438 @@
+using System.Text;
+using ABI_RC.Core.Player;
+using ABI_RC.Core.Networking.IO.Social;
+using ABI_RC.Core.UI;
+using ABI_RC.Core.UI.Hud;
+using ABI_RC.Systems.ChatBox;
+using ABI_RC.Systems.Communications.Settings;
+using ABI_RC.Systems.GameEventSystem;
+using ABI_RC.Systems.PlayerColors;
+using MelonLoader;
+using UnityEngine;
+
+namespace NAK.ChatBoxHud;
+
+public class ChatBoxHudMod : MelonMod
+{
+ public enum HudTarget { Disabled, LookAt, Nearest, LookAtThenNearest }
+ public enum SourceFilter { None, FriendsOnly, Everyone }
+ public enum MaxUsersOnHud { One, Two, Three }
+
+ private const int MaxLines = 4;
+ private const int BaseCPL = 62;
+ private const int BaseSize = 80;
+ private const int MinSize = 50;
+ private const int ShrinkAt = 3;
+ private const float MinLife = 5f;
+ private const float MaxLife = 20f;
+ private const float WPM = 250f;
+ private const float CPW = 5f;
+ private const float LookAngle = 20f;
+ private const float FadePct = 0.2f;
+ private const int MsgCap = 1000;
+
+ private static readonly MelonPreferences_Category Cat =
+ MelonPreferences.CreateCategory(nameof(ChatBoxHud));
+
+ internal static readonly MelonPreferences_Entry EntryHudMode =
+ Cat.CreateEntry("hud_mode", HudTarget.LookAtThenNearest, "HUD Target Mode");
+
+ internal static readonly MelonPreferences_Entry EntryHudFilter =
+ Cat.CreateEntry("hud_filter", SourceFilter.Everyone, "Chat Filter",
+ description: "Whose chat messages appear on the HUD.");
+
+ internal static readonly MelonPreferences_Entry EntryMaxUsersOnHud =
+ Cat.CreateEntry("max_users_on_hud", MaxUsersOnHud.Three, "Max Users on HUD",
+ description: "How many users can be stacked on the HUD when multiple users chat at once.");
+
+ internal static readonly MelonPreferences_Entry EntryUsePlayerColors =
+ Cat.CreateEntry("player_colors", true, "Use Player Colors",
+ description: "Use player-chosen colors for names instead of friend colors.");
+
+ private static readonly Dictionary Buffers = new();
+ private static readonly StringBuilder SB = new();
+ private static readonly int[] VisSlots = new int[MaxLines * 3 + 1];
+
+ private static PortalHudIndicator _hud;
+
+ #region Slot System
+
+ // Stable slot list: once a user occupies an index they keep it until
+ // all their messages expire or they leave range. New users append to
+ // the first open position. This prevents blocks from jumping around
+ // when someone else sends a new message.
+ private static readonly List _slotIds = new();
+ private static bool _hudActive;
+
+ private struct Candidate
+ {
+ public string Id, Name;
+ public Vector3 Pos;
+ public float Score; // lower = higher priority
+ }
+
+ private static readonly List _candidates = new();
+ private static readonly Dictionary _candidateLookup = new();
+
+ #endregion
+
+ private struct Incoming
+ {
+ public string Id, Name, Text;
+ public ChatBoxAPI.MessageSource Source;
+ }
+
+ private class Msg
+ {
+ public string[] Lines;
+ public int Size;
+ public float Stamp, Life;
+ public ChatBoxAPI.MessageSource Source;
+ }
+
+ private class ChatBuf
+ {
+ public string Id;
+ public readonly List Msgs = new();
+ }
+
+ public override void OnInitializeMelon()
+ {
+ ChatBoxAPI.OnMessageReceived += OnReceive;
+ CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(OnPlayerSetupStart);
+ }
+
+ private static void OnPlayerSetupStart()
+ {
+ Transform src = HudController.Instance.PortalHudIndicator.transform;
+ GameObject go = UnityEngine.Object.Instantiate(src.gameObject, src.parent);
+ go.transform.SetLocalPositionAndRotation(src.localPosition, src.localRotation);
+ go.transform.localScale = src.localScale;
+ _hud = go.GetComponent();
+ }
+
+ public override void OnUpdate()
+ {
+ Tick();
+ }
+
+ private static void Tick()
+ {
+ if (_hud == null || EntryHudMode.Value == HudTarget.Disabled) { Hide(); return; }
+ Prune();
+
+ int maxSlots = (int)EntryMaxUsersOnHud.Value + 1;
+ GatherCandidates();
+
+ // Remove slots whose user has no messages left or is no longer
+ // a valid candidate (walked out of range, left the instance, etc.)
+ for (int i = _slotIds.Count - 1; i >= 0; i--)
+ {
+ string sid = _slotIds[i];
+ bool dead = !_candidateLookup.ContainsKey(sid)
+ || !Buffers.TryGetValue(sid, out ChatBuf b)
+ || b.Msgs.Count == 0;
+ if (dead) _slotIds.RemoveAt(i);
+ }
+
+ // If the setting was lowered at runtime, trim from the end
+ while (_slotIds.Count > maxSlots)
+ _slotIds.RemoveAt(_slotIds.Count - 1);
+
+ // Fill empty slots from best-scored candidates not already slotted
+ if (_slotIds.Count < maxSlots && _candidates.Count > 0)
+ {
+ _candidates.Sort((a, b) => a.Score.CompareTo(b.Score));
+ foreach (Candidate c in _candidates)
+ {
+ if (_slotIds.Count >= maxSlots) break;
+ if (_slotIds.Contains(c.Id)) continue;
+ _slotIds.Add(c.Id);
+ }
+ }
+
+ if (_slotIds.Count == 0) { Hide(); return; }
+
+ // Render every active slot into one combined string
+ SB.Clear();
+ bool first = true;
+ foreach (string slotId in _slotIds)
+ {
+ if (!_candidateLookup.TryGetValue(slotId, out Candidate cand)) continue;
+ if (!Buffers.TryGetValue(slotId, out ChatBuf buf) || buf.Msgs.Count == 0) continue;
+
+ if (!first) SB.Append('\n');
+ RenderBlock(SB, cand.Name, cand.Id, buf, cand.Pos);
+ first = false;
+ }
+
+ _hud.SetText(SB.ToString());
+ if (!_hudActive) { _hudActive = true; _hud.Activate(); }
+ }
+
+ private static void Hide()
+ {
+ if (!_hudActive) return;
+ _hud?.Deactivate();
+ _hudActive = false;
+ _slotIds.Clear();
+ }
+
+ // Scores every player who has buffered messages, is in comms range,
+ // and passes the current HudTarget mode filter.
+ private static void GatherCandidates()
+ {
+ _candidates.Clear();
+ _candidateLookup.Clear();
+
+ HudTarget mode = EntryHudMode.Value;
+ Camera cam = Camera.main;
+ if (cam == null) return;
+
+ Vector3 lp = PlayerSetup.Instance.GetPlayerPosition();
+ Vector3 cf = cam.transform.forward;
+ Vector3 cp = cam.transform.position;
+ float commsRange = Comms_SettingsHandler.FalloffDistance + 1;
+
+ foreach (KeyValuePair kvp in Buffers)
+ {
+ if (kvp.Value.Msgs.Count == 0) continue;
+ if (!CVRPlayerManager.Instance.TryGetPlayerBase(kvp.Key, out PlayerBase pb)) continue;
+
+ Vector3 pp = pb.GetPlayerWorldRootPosition();
+ float d = Vector3.Distance(lp, pp);
+ if (d > commsRange) continue;
+
+ string uid = kvp.Key;
+ string n = pb.PlayerUsername ?? "???";
+ float angle = Vector3.Angle(cf, (pp - cp).normalized);
+ bool inLook = angle < LookAngle;
+
+ float score;
+ switch (mode)
+ {
+ case HudTarget.LookAt:
+ if (!inLook) continue; // hard filter
+ score = angle;
+ break;
+ case HudTarget.Nearest:
+ score = d;
+ break;
+ case HudTarget.LookAtThenNearest:
+ // look-at targets always rank above distance-only
+ score = inLook ? angle : 1000f + d;
+ break;
+ default:
+ continue;
+ }
+
+ Candidate c = new Candidate { Id = uid, Name = n, Pos = pp, Score = score };
+ _candidates.Add(c);
+ _candidateLookup[uid] = c;
+ }
+ }
+
+ private static void RenderBlock(StringBuilder sb, string playerName, string playerId,
+ ChatBuf buf, Vector3 tgtPos)
+ {
+ Camera cam = PlayerSetup.Instance.activeCam;
+ float now = Time.time;
+
+ // direction arrows from camera angle to target
+ float la = 0f, ra = 0f;
+ {
+ Vector3 toT = tgtPos - cam.transform.position;
+ Vector3 fwd = cam.transform.forward;
+ toT.y = 0f;
+ fwd.y = 0f;
+ if (toT.sqrMagnitude > 0.001f && fwd.sqrMagnitude > 0.001f)
+ {
+ float sa = Vector3.SignedAngle(fwd.normalized, toT.normalized, Vector3.up);
+ float abs = Mathf.Abs(sa);
+ if (abs > LookAngle)
+ {
+ float t = Mathf.InverseLerp(LookAngle, 90f, abs);
+ float v = Mathf.Clamp01(t);
+ la = sa < 0f ? v : 0f;
+ ra = sa > 0f ? v : 0f;
+ }
+ }
+ }
+
+ // name color: player colors or friend-based fallback
+ string hex;
+ if (EntryUsePlayerColors.Value)
+ {
+ PlayerColors pc = PlayerColorsManager.GetPlayerColors(playerId);
+ hex = "#" + ColorUtility.ToHtmlStringRGB(pc.PrimaryColor);
+ }
+ else hex = Friends.FriendsWith(playerId) ? "#4FC3F7" : "#E0E0E0";
+
+ // header line
+ AppendAlpha(sb, la); sb.Append("\u25C4 ");
+ sb.Append("')
+ .Append(Esc(playerName)).Append("");
+ AppendAlpha(sb, ra); sb.Append(" \u25BA\n");
+
+ // allocate visible line count per message, newest gets priority
+ int count = 0, budget = MaxLines;
+ for (int i = buf.Msgs.Count - 1; i >= 0 && budget > 0; i--)
+ {
+ int v = Mathf.Min(buf.Msgs[i].Lines.Length, budget);
+ VisSlots[count++] = v;
+ budget -= v;
+ }
+
+ // render oldest to newest (slots were stored newest-first, so reverse)
+ int startIdx = buf.Msgs.Count - count;
+ for (int i = 0; i < count; i++)
+ {
+ int mi = startIdx + i;
+ int vis = VisSlots[count - 1 - i];
+ Msg m = buf.Msgs[mi];
+ float age = now - m.Stamp;
+
+ float fadeAt = m.Life * (1f - FadePct);
+ float alpha = age < fadeAt ? 1f
+ : Mathf.Clamp01(1f - (age - fadeAt) / (m.Life * FadePct));
+ byte a = (byte)(alpha * 255);
+
+ // smooth line-by-line scroll for overflowing messages
+ int off = 0;
+ if (m.Lines.Length > vis)
+ {
+ float usable = m.Life * (1f - FadePct);
+ float perLine = usable / m.Lines.Length;
+ float initHold = perLine * vis;
+ off = Mathf.Min(
+ (int)(Mathf.Max(0f, age - initHold) / perLine),
+ m.Lines.Length - vis);
+ }
+
+ for (int j = 0; j < vis; j++)
+ {
+ sb.Append("')
+ .Append("")
+ .Append(m.Lines[off + j])
+ .Append("\n");
+ }
+ }
+
+ // trim trailing newline from this block
+ if (sb.Length > 0 && sb[sb.Length - 1] == '\n') sb.Length--;
+ }
+
+ private static void AppendAlpha(StringBuilder sb, float a01)
+ {
+ sb.Append("');
+ }
+
+ private static void OnReceive(ChatBoxAPI.ChatBoxMessage msg)
+ {
+ if (msg.Source != ChatBoxAPI.MessageSource.Internal) return;
+ if (string.IsNullOrWhiteSpace(msg.Message)) return;
+
+ string sid = msg.SenderGuid;
+ if (!CVRPlayerManager.Instance.TryGetPlayerBase(sid, out PlayerBase pb)) return;
+
+ SourceFilter filter = EntryHudFilter.Value;
+ if (filter == SourceFilter.None) return;
+ if (filter == SourceFilter.FriendsOnly && !Friends.FriendsWith(sid)) return;
+
+ Store(new Incoming
+ {
+ Id = sid,
+ Name = pb.PlayerUsername ?? "???",
+ Text = msg.Message,
+ Source = msg.Source
+ });
+ }
+
+ private static void Store(in Incoming inc)
+ {
+ if (!Buffers.TryGetValue(inc.Id, out ChatBuf buf))
+ {
+ buf = new ChatBuf { Id = inc.Id };
+ Buffers[inc.Id] = buf;
+ }
+
+ string text = inc.Text;
+ if (text.Length > MsgCap) text = text.Substring(0, MsgCap);
+ text = text.Replace("\r", "");
+
+ Fit(text, out string[] lines, out int size);
+ for (int i = 0; i < lines.Length; i++) lines[i] = Esc(lines[i]);
+
+ buf.Msgs.Add(new Msg
+ {
+ Lines = lines,
+ Size = size,
+ Stamp = Time.time,
+ Life = Life(text.Length),
+ Source = inc.Source
+ });
+
+ while (buf.Msgs.Count > MaxLines * 3)
+ buf.Msgs.RemoveAt(0);
+ }
+
+ private static void Prune()
+ {
+ float now = Time.time;
+ foreach (ChatBuf buf in Buffers.Values)
+ buf.Msgs.RemoveAll(m => now - m.Stamp > m.Life);
+ }
+
+ // shrink text size until line count is manageable
+ private static void Fit(string text, out string[] lines, out int size)
+ {
+ lines = Wrap(text, BaseCPL);
+ size = BaseSize;
+ if (lines.Length <= ShrinkAt) return;
+
+ for (int s = BaseSize - 5; s >= MinSize; s -= 5)
+ {
+ int cpl = (int)(BaseCPL * BaseSize / (float)s);
+ string[] attempt = Wrap(text, cpl);
+ lines = attempt; size = s;
+ if (attempt.Length <= ShrinkAt) return;
+ }
+ }
+
+ // word wrap respecting embedded newlines
+ private static string[] Wrap(string text, int max)
+ {
+ if (string.IsNullOrEmpty(text)) return new[] { string.Empty };
+
+ List result = new List(4);
+ foreach (string seg in text.Split('\n'))
+ {
+ if (seg.Length <= max) { result.Add(seg); continue; }
+
+ StringBuilder cur = new StringBuilder(max);
+ foreach (string w in seg.Split(' '))
+ {
+ if (w.Length == 0) continue;
+ if (w.Length > max)
+ {
+ if (cur.Length > 0) { result.Add(cur.ToString()); cur.Clear(); }
+ for (int c = 0; c < w.Length; c += max)
+ result.Add(w.Substring(c, Math.Min(max, w.Length - c)));
+ continue;
+ }
+ if (cur.Length == 0) cur.Append(w);
+ else if (cur.Length + 1 + w.Length <= max) cur.Append(' ').Append(w);
+ else { result.Add(cur.ToString()); cur.Clear(); cur.Append(w); }
+ }
+ if (cur.Length > 0) result.Add(cur.ToString());
+ }
+ return result.ToArray();
+ }
+
+ private static float Life(int chars)
+ {
+ return Mathf.Clamp(MinLife + chars * (60f / (WPM * CPW)), MinLife, MaxLife);
+ }
+
+ private static string Esc(string s) => s.Replace("<", "<\u200B");
+}
\ No newline at end of file
diff --git a/ChatBoxHud/Properties/AssemblyInfo.cs b/ChatBoxHud/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a6cd27b
--- /dev/null
+++ b/ChatBoxHud/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+using MelonLoader;
+using NAK.ChatBoxHud.Properties;
+using System.Reflection;
+
+[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyTitle(nameof(NAK.ChatBoxHud))]
+[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
+[assembly: AssemblyProduct(nameof(NAK.ChatBoxHud))]
+
+[assembly: MelonInfo(
+ typeof(NAK.ChatBoxHud.ChatBoxHudMod),
+ nameof(NAK.ChatBoxHud),
+ AssemblyInfoParams.Version,
+ AssemblyInfoParams.Author,
+ downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ChatBoxHud"
+)]
+
+[assembly: MelonGame("ChilloutVR", "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.ChatBoxHud.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/ChatBoxHud/README.md b/ChatBoxHud/README.md
new file mode 100644
index 0000000..126be0a
--- /dev/null
+++ b/ChatBoxHud/README.md
@@ -0,0 +1,14 @@
+# ChatBoxHud
+
+Shows nearby ChatBox messages directly on your HUD so you can read them comfortably in VR without breaking your neck.
+
+---
+
+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/ChatBoxHud/format.json b/ChatBoxHud/format.json
new file mode 100644
index 0000000..cd46a8b
--- /dev/null
+++ b/ChatBoxHud/format.json
@@ -0,0 +1,23 @@
+{
+ "_id": -1,
+ "name": "ChatBoxHud",
+ "modversion": "1.0.0",
+ "gameversion": "2026r181",
+ "loaderversion": "0.7.2",
+ "modtype": "Mod",
+ "author": "NotAKidoS",
+ "description": "Shows nearby ChatBox messages directly on your HUD so you can read them comfortably in VR without breaking your neck.",
+ "searchtags": [
+ "chat",
+ "box",
+ "hud",
+ "indicator",
+ "captions"
+ ],
+ "requirements": [
+ ],
+ "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r49/ChatBoxHud.dll",
+ "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ChatBoxHud/",
+ "changelog": "- Initial release",
+ "embedcolor": "#f61963"
+}
\ No newline at end of file
From 2b8f17b2872ad8e65cb11a8e4b2da200b8176e9b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Wed, 15 Apr 2026 23:45:24 +0000
Subject: [PATCH 2/2] [NAK_CVR_Mods] Update mod list in README
---
README.md | 39 ++++++++++++++++++++-------------------
1 file changed, 20 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index 79bc57d..9b18896 100644
--- a/README.md
+++ b/README.md
@@ -6,25 +6,26 @@
| Name | Description | Download |
|------|-------------|----------|
-| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ASTExtension.dll) |
+| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | No Download |
+| [ChatBoxHud](ChatBoxHud/README.md) | Shows nearby ChatBox messages directly on your HUD so you can read them comfortably in VR without breaking your neck. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r49/ChatBoxHud.dll) |
| [ConfigureCalibrationPose](ConfigureCalibrationPose/README.md) | Select FBT calibration pose. | No Download |
-| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/CustomSpawnPoint.dll) |
-| [DesktopInteractions](DesktopInteractions/README.md) | Adds IK-driven hand gestures to your avatar in Desktop: earpiece grab (GMOD-style) when typing in ChatBox, and binocular cupping when zooming. Both gestures are toggleable in settings. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/DesktopInteractions.dll) |
-| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/DoubleTapJumpToExitSeat.dll) |
-| [ESCBothMenus](ESCBothMenus/README.md) | Makes the Quick Menu appear when pressing ESC in Desktop. Pressing twice will open straight to Main Menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ESCBothMenus.dll) |
-| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/FuckToes.dll) |
-| [PlapPlapForAll](PlapPlapForAll/README.md) | Penetrator SFX mod which adds Noach's PlapPlap prefab to any detected DPS setup on avatars that do not already have it. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/PlapPlapForAll.dll) |
-| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/PropLoadingHexagon.dll) |
-| [PropsButBetter](PropsButBetter/README.md) | Prop quality-of-life suite. Adds a few new ways to interact with and manage Props. | No Download |
-| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | 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. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/RCCVirtualSteeringWheel.dll) |
-| [RelativeSyncJitterFix](RelativeSyncJitterFix/README.md) | Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/RelativeSyncJitterFix.dll) |
-| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ShareBubbles.dll) |
-| [ShowPlayerInSelfMirror](ShowPlayerInSelfMirror/README.md) | Adds an option in the Quick Menu selected player page to show the target player's avatar in your self mirror. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ShowPlayerInSelfMirror.dll) |
-| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/SmootherRay.dll) |
+| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | No Download |
+| [DesktopInteractions](DesktopInteractions/README.md) | Adds IK-driven hand gestures to your avatar in Desktop: earpiece grab (GMOD-style) when typing in ChatBox, and binocular cupping when zooming. Both gestures are toggleable in settings. | No Download |
+| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | No Download |
+| [ESCBothMenus](ESCBothMenus/README.md) | Makes the Quick Menu appear when pressing ESC in Desktop. Pressing twice will open straight to Main Menu. | No Download |
+| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | No Download |
+| [PlapPlapForAll](PlapPlapForAll/README.md) | Penetrator SFX mod which adds Noach's PlapPlap prefab to any detected DPS setup on avatars that do not already have it. | No Download |
+| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | No Download |
+| [PropsButBetter](PropsButBetter/README.md) | Prop quality-of-life suite. Adds a few new ways to interact with and manage Props. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r49/PropsButBetter.dll) |
+| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | 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. | No Download |
+| [RelativeSyncJitterFix](RelativeSyncJitterFix/README.md) | Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. | No Download |
+| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | No Download |
+| [ShowPlayerInSelfMirror](ShowPlayerInSelfMirror/README.md) | Adds an option in the Quick Menu selected player page to show the target player's avatar in your self mirror. | No Download |
+| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | No Download |
| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | No Download |
-| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ThirdPerson.dll) |
-| [Tinyboard](Tinyboard/README.md) | Makes the keyboard small and smart. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/Tinyboard.dll) |
-| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/YouAreMyPropNowWeAreHavingSoftTacosLater.dll) |
+| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | No Download |
+| [Tinyboard](Tinyboard/README.md) | Makes the keyboard small and smart. | No Download |
+| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | No Download |
### Experimental Mods
@@ -33,9 +34,9 @@
| [ByeByePerformanceThankYouAMD](.Experimental/ByeByePerformanceThankYouAMD/README.md) | Fixes search terms that use spaces. | No Download |
| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | No Download |
| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | No Download |
-| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/LuaNetworkVariables.dll) |
+| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. | No Download |
| [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | No Download |
-| [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/OriginShift.dll) |
+| [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | No Download |
| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | No Download |