From b94a33e75d42752c6f9837cf436870c3640cba4e Mon Sep 17 00:00:00 2001 From: loukylor <46853114+loukylor@users.noreply.github.com> Date: Thu, 11 Mar 2021 09:12:05 -0800 Subject: [PATCH] Add files via upload --- .../Components/EnableDisableListener.cs | 18 + PlayerList/Config.cs | 67 +++ PlayerList/Entries/CoordinatePositionEntry.cs | 16 + PlayerList/Entries/EntryBase.cs | 63 ++ PlayerList/Entries/GameVersionEntry.cs | 14 + PlayerList/Entries/InstanceCreatorEntry.cs | 53 ++ PlayerList/Entries/InstanceMasterEntry.cs | 26 + PlayerList/Entries/LocalPlayerEntry.cs | 65 +++ PlayerList/Entries/PlayerEntry.cs | 215 +++++++ PlayerList/Entries/PlayerListHeaderEntry.cs | 9 + PlayerList/Entries/RoomTimeEntry.cs | 15 + PlayerList/Entries/SystemTime12HrEntry.cs | 12 + PlayerList/Entries/SystemTime24HrEntry.cs | 11 + PlayerList/Entries/WorldAuthorEntry.cs | 9 + PlayerList/Entries/WorldNameEntry.cs | 9 + PlayerList/InputManager.cs | 41 ++ PlayerList/NetworkHooks.cs | 23 + PlayerList/PlayerList.csproj | 66 +++ PlayerList/PlayerListMod.cs | 542 ++++++++++++++++++ PlayerList/UI/Label.cs | 39 ++ PlayerList/UI/QuarterButton.cs | 26 + PlayerList/UI/SingleButton.cs | 42 ++ PlayerList/UI/SubMenu.cs | 25 + PlayerList/UI/ToggleButton.cs | 112 ++++ PlayerList/UI/UIManager.cs | 122 ++++ PlayerList/Utilities/Constants.cs | 16 + PlayerList/Utilities/Converters.cs | 28 + PlayerList/Utilities/Xref.cs | 32 ++ PlayerList/playerlistmod.assetbundle | Bin 0 -> 68723 bytes PlayerList/playerlistmod.assetbundle.manifest | 47 ++ 30 files changed, 1763 insertions(+) create mode 100644 PlayerList/Components/EnableDisableListener.cs create mode 100644 PlayerList/Config.cs create mode 100644 PlayerList/Entries/CoordinatePositionEntry.cs create mode 100644 PlayerList/Entries/EntryBase.cs create mode 100644 PlayerList/Entries/GameVersionEntry.cs create mode 100644 PlayerList/Entries/InstanceCreatorEntry.cs create mode 100644 PlayerList/Entries/InstanceMasterEntry.cs create mode 100644 PlayerList/Entries/LocalPlayerEntry.cs create mode 100644 PlayerList/Entries/PlayerEntry.cs create mode 100644 PlayerList/Entries/PlayerListHeaderEntry.cs create mode 100644 PlayerList/Entries/RoomTimeEntry.cs create mode 100644 PlayerList/Entries/SystemTime12HrEntry.cs create mode 100644 PlayerList/Entries/SystemTime24HrEntry.cs create mode 100644 PlayerList/Entries/WorldAuthorEntry.cs create mode 100644 PlayerList/Entries/WorldNameEntry.cs create mode 100644 PlayerList/InputManager.cs create mode 100644 PlayerList/NetworkHooks.cs create mode 100644 PlayerList/PlayerList.csproj create mode 100644 PlayerList/PlayerListMod.cs create mode 100644 PlayerList/UI/Label.cs create mode 100644 PlayerList/UI/QuarterButton.cs create mode 100644 PlayerList/UI/SingleButton.cs create mode 100644 PlayerList/UI/SubMenu.cs create mode 100644 PlayerList/UI/ToggleButton.cs create mode 100644 PlayerList/UI/UIManager.cs create mode 100644 PlayerList/Utilities/Constants.cs create mode 100644 PlayerList/Utilities/Converters.cs create mode 100644 PlayerList/Utilities/Xref.cs create mode 100644 PlayerList/playerlistmod.assetbundle create mode 100644 PlayerList/playerlistmod.assetbundle.manifest diff --git a/PlayerList/Components/EnableDisableListener.cs b/PlayerList/Components/EnableDisableListener.cs new file mode 100644 index 0000000..cf63e73 --- /dev/null +++ b/PlayerList/Components/EnableDisableListener.cs @@ -0,0 +1,18 @@ +using System; +using UnhollowerBaseLib.Attributes; +using UnityEngine; + +namespace PlayerList.Components +{ + class EnableDisableListener : MonoBehaviour + { + [method: HideFromIl2Cpp] + public event Action OnEnableEvent; + [method: HideFromIl2Cpp] + public event Action OnDisableEvent; + public EnableDisableListener(IntPtr obj0) : base(obj0) { } + + public void OnEnable() => OnEnableEvent?.Invoke(); + public void OnDisable() => OnDisableEvent?.Invoke(); + } +} diff --git a/PlayerList/Config.cs b/PlayerList/Config.cs new file mode 100644 index 0000000..9d52210 --- /dev/null +++ b/PlayerList/Config.cs @@ -0,0 +1,67 @@ +using MelonLoader; +using UnityEngine; + +namespace PlayerList +{ + class Config + { + // TODO: Make is so the vector 2 acutlaly uses the custom mapper when it gets fixed + public static readonly string categoryIdentifier = "PlayerList Config"; + public static MelonPreferences_Category category = MelonPreferences.CreateCategory(categoryIdentifier); + + public static MelonPreferences_Entry enabledOnStart; + public static MelonPreferences_Entry condensedText; + public static MelonPreferences_Entry fontSize; + public static MelonPreferences_Entry snapToGridSize; + + public static MelonPreferences_Entry pingToggle; + public static MelonPreferences_Entry fpsToggle; + public static MelonPreferences_Entry platformToggle; + public static MelonPreferences_Entry perfToggle; + public static MelonPreferences_Entry displayNameToggle; + public static bool HasSomethingOff + { + get + { + if (!pingToggle.Value || !fpsToggle.Value || !platformToggle.Value || !perfToggle.Value || !displayNameToggle.Value) + return true; + return false; + } + } + + public static MelonPreferences_Entry playerListMenuButtonPosition; + + private static MelonPreferences_Entry _playerListPositionX; + private static MelonPreferences_Entry _playerListPositionY; + public static Vector2 PlayerListPosition + { + get { return Utilities.Converters.ConvertToUnityUnits(new Vector2(_playerListPositionX.Value, _playerListPositionY.Value)); } + set + { + Vector2 convertedVector = Utilities.Converters.ConvertToMenuUnits(value); + _playerListPositionX.Value = convertedVector.x; + _playerListPositionY.Value = convertedVector.y; + } + } + + public static void RegisterSettings() + { + enabledOnStart = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(enabledOnStart), true, is_hidden: true); + condensedText = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(condensedText), false, is_hidden: true); + fontSize = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(fontSize), 35, is_hidden: true); + snapToGridSize = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(snapToGridSize), 420, is_hidden: true); + + pingToggle = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(pingToggle), true, is_hidden: true); + fpsToggle = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(fpsToggle), true, is_hidden: true); + platformToggle = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(platformToggle), true, is_hidden: true); + perfToggle = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(perfToggle), true, is_hidden: true); + displayNameToggle = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(displayNameToggle), true, is_hidden: true); + + playerListMenuButtonPosition = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(playerListMenuButtonPosition), 1, is_hidden: true); + + _playerListPositionX = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(_playerListPositionX), 7.5f, is_hidden: true); + _playerListPositionY = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(_playerListPositionY), 3.5f, is_hidden: true); + // playerListPosition = (MelonPreferences_Entry)MelonPreferences.CreateEntry(categoryIdentifier, nameof(playerListPosition), (Vector2)Utilities.Converters.ConvertToUnityUnits(new Vector3(7.5f, 3.5f)), is_hidden: true); + } + } +} diff --git a/PlayerList/Entries/CoordinatePositionEntry.cs b/PlayerList/Entries/CoordinatePositionEntry.cs new file mode 100644 index 0000000..623466c --- /dev/null +++ b/PlayerList/Entries/CoordinatePositionEntry.cs @@ -0,0 +1,16 @@ +using System; +using VRC; + +namespace PlayerList.Entries +{ + class CoordinatePositionEntry : EntryBase + { + public override string Name { get { return "Coordinate Position"; } } + public override void ProcessText(object[] parameters = null) + { + ChangeEntry("x", Math.Round(Player.prop_Player_0.gameObject.transform.position.x, 1)); + ChangeEntry("y", Math.Round(Player.prop_Player_0.gameObject.transform.position.y, 1)); + ChangeEntry("z", Math.Round(Player.prop_Player_0.gameObject.transform.position.z, 1)); + } + } +} diff --git a/PlayerList/Entries/EntryBase.cs b/PlayerList/Entries/EntryBase.cs new file mode 100644 index 0000000..03d2211 --- /dev/null +++ b/PlayerList/Entries/EntryBase.cs @@ -0,0 +1,63 @@ +using MelonLoader; +using UnityEngine; +using UnityEngine.UI; + +namespace PlayerList.Entries +{ + public class EntryBase + { + public virtual string Name { get; } + + public MelonPreferences_Entry prefEntry; + + private string _originalText; + public string OriginalText + { + get { return _originalText; } + } + + public GameObject gameObject; + public Text textComponent; + + private int _identifier; + public int Identifier + { + get { return _identifier; } + } + + public virtual void Init(object[] parameters = null) + { + + } + + public void Refresh(object[] parameters = null) + { + textComponent.text = _originalText; + ProcessText(parameters); + } + + public virtual void ProcessText(object[] parameters = null) + { + } + + public static T CreateInstance(GameObject gameObject, object[] parameters = null, bool includeConfig = false) where T : EntryBase, new() + { + EntryBase entry = new T(); + + entry._identifier = gameObject.GetInstanceID(); + entry.gameObject = gameObject; + entry.textComponent = gameObject.GetComponent(); + entry._originalText = entry.textComponent.text; + if (includeConfig) + { + entry.prefEntry = (MelonPreferences_Entry)MelonPreferences.CreateEntry(Config.categoryIdentifier, entry.Name.Replace(" ", ""), true, is_hidden: true); + entry.gameObject.SetActive(entry.prefEntry.Value); + } + entry.Init(parameters); + + return (T)entry; + } + public void ChangeEntry(string identifier, string value) => textComponent.text = textComponent.text.Replace($"{{{identifier}}}", value); + public void ChangeEntry(string identifier, object value) => textComponent.text = textComponent.text.Replace($"{{{identifier}}}", value.ToString()); + } +} diff --git a/PlayerList/Entries/GameVersionEntry.cs b/PlayerList/Entries/GameVersionEntry.cs new file mode 100644 index 0000000..d9ddb23 --- /dev/null +++ b/PlayerList/Entries/GameVersionEntry.cs @@ -0,0 +1,14 @@ +using System.Linq; +using UnityEngine; + +namespace PlayerList.Entries +{ + class GameVersionEntry : EntryBase + { + public override string Name { get { return "Game Version"; } } + + public static int buildNumber = Resources.FindObjectsOfTypeAll().First().field_Public_Int32_0; + + public override void ProcessText(object[] parameters = null) => ChangeEntry("gameversion", buildNumber); + } +} diff --git a/PlayerList/Entries/InstanceCreatorEntry.cs b/PlayerList/Entries/InstanceCreatorEntry.cs new file mode 100644 index 0000000..cbdd75e --- /dev/null +++ b/PlayerList/Entries/InstanceCreatorEntry.cs @@ -0,0 +1,53 @@ +using System; +using VRC.Core; + +namespace PlayerList.Entries +{ + class InstanceCreatorEntry : EntryBase + { + public override string Name { get { return "Instance Creator"; } } + + public string creatorTag; + public string lastUserDisplayName; + public override void ProcessText(object[] parameters = null) + { + if (RoomManager.field_Internal_Static_ApiWorldInstance_0 != null) + { + Il2CppSystem.Collections.Generic.List tags = RoomManager.field_Internal_Static_ApiWorldInstance_0.ParseTags(RoomManager.field_Internal_Static_ApiWorldInstance_0.idWithTags); + foreach (ApiWorldInstance.InstanceTag tag in tags) + { + if (tag.name == "private" || tag.name == "friend" || tag.name == "hidden") + { + if (creatorTag == tag.data) + { + ChangeEntry("instancecreator", lastUserDisplayName); + return; + } + + if (tag.data == APIUser.CurrentUser.id) + { + ChangeEntry("instancecreator", APIUser.CurrentUser.displayName); + lastUserDisplayName = APIUser.CurrentUser.displayName; + } + else + { + APIUser.FetchUser(tag.data, new Action(OnIdReceived), null); + ChangeEntry("instancecreator", "Loading..."); + } + + creatorTag = tag.data; + return; + } + } + } + + ChangeEntry("instancecreator", "No Instance Creator"); + creatorTag = null; + } + public void OnIdReceived(APIUser user) + { + lastUserDisplayName = user.displayName; + MelonLoader.MelonLogger.Msg("User fetched"); + } + } +} diff --git a/PlayerList/Entries/InstanceMasterEntry.cs b/PlayerList/Entries/InstanceMasterEntry.cs new file mode 100644 index 0000000..8b8d646 --- /dev/null +++ b/PlayerList/Entries/InstanceMasterEntry.cs @@ -0,0 +1,26 @@ +using VRC; + +namespace PlayerList.Entries +{ + class InstanceMasterEntry : EntryBase + { + public override string Name { get { return "Instance Master"; } } + + public override void ProcessText(object[] parameters = null) + { + if (PlayerManager.field_Private_Static_PlayerManager_0 == null) return; + + foreach (Player player in PlayerManager.field_Private_Static_PlayerManager_0.field_Private_List_1_Player_0) + { + if (player.prop_VRCPlayerApi_0 == null) return; + + if (player.prop_VRCPlayerApi_0.isMaster) + { + if (player.field_Private_APIUser_0 == null) return; + ChangeEntry("instancemaster", player.field_Private_APIUser_0.displayName); + return; + } + } + } + } +} diff --git a/PlayerList/Entries/LocalPlayerEntry.cs b/PlayerList/Entries/LocalPlayerEntry.cs new file mode 100644 index 0000000..33ede1a --- /dev/null +++ b/PlayerList/Entries/LocalPlayerEntry.cs @@ -0,0 +1,65 @@ +using UnityEngine; +using VRC; +using VRC.Core; + +namespace PlayerList.Entries +{ + class LocalPlayerEntry : EntryBase + { + public override string Name { get { return "Local Player"; } } + + public TMPro.TextMeshProUGUI perfText; + + public override void Init(object[] parameters) + { + gameObject.GetComponent().onClick.AddListener(new System.Action(() => PlayerEntry.OpenPlayerInQuickMenu(Player.prop_Player_0))); + perfText = Player.prop_Player_0.transform.Find("Player Nameplate/Canvas/Nameplate/Contents/Quick Stats/Performance Text").GetComponent(); + } + public override void ProcessText(object[] parameters) + { + if (Config.condensedText.Value && !Config.HasSomethingOff) + textComponent.text = textComponent.text.Replace(" ", ""); + + // Convert to byte as that's what's sent over network so if you spoof your ping you'll see what's actually sent over the network + if (Config.pingToggle.Value) + { + ChangeEntry("pingcolor", PlayerEntry.GetPingColor((short)Photon.Pun.PhotonNetwork.field_Public_Static_LoadBalancingClient_0.prop_LoadBalancingPeer_0.RoundTripTime)); + ChangeEntry("ping", ((short)Photon.Pun.PhotonNetwork.field_Public_Static_LoadBalancingClient_0.prop_LoadBalancingPeer_0.RoundTripTime).ToString().PadRight(4)); + } + + // I STG if I have to remove fps because skids start walking up to people saying poeple's fps im gonna murder someone + if (Config.fpsToggle.Value) + { + ChangeEntry("fpscolor", PlayerEntry.GetFpsColor((int)(1f / Time.deltaTime))); + ChangeEntry("fps", ((int)(1f / Time.deltaTime)).ToString().PadRight(3)); + } + + if (Config.platformToggle.Value) + ChangeEntry("platform", PlayerEntry.ParsePlatform(Player.prop_Player_0).PadRight(5)); + + if (Config.perfToggle.Value) + { + if (perfText != null) + { + ChangeEntry("perfcolor", "#" + ColorUtility.ToHtmlStringRGB(perfText.color)); + ChangeEntry("perf", PlayerEntry.ParsePerformanceText(perfText.text).PadRight(5)); + } + else + { + perfText = Player.prop_Player_0.transform.Find("Player Nameplate/Canvas/Nameplate/Contents/Quick Stats/Performance Text").GetComponent(); + ChangeEntry("perfcolor", "#ff00000"); + ChangeEntry("perf", "???"); + } + } + + if (Config.displayNameToggle.Value) + { + ChangeEntry("rankcolor", "#" + ColorUtility.ToHtmlStringRGB(VRCPlayer.Method_Public_Static_Color_APIUser_0(APIUser.CurrentUser))); + ChangeEntry("displayname", APIUser.CurrentUser.displayName); + } + + if (Config.HasSomethingOff) + textComponent.text = " - " + PlayerEntry.RemoveOffToggles(textComponent.text.Substring(3)); + } + } +} diff --git a/PlayerList/Entries/PlayerEntry.cs b/PlayerList/Entries/PlayerEntry.cs new file mode 100644 index 0000000..01e2d94 --- /dev/null +++ b/PlayerList/Entries/PlayerEntry.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using VRC; + +namespace PlayerList.Entries +{ + public class PlayerEntry : EntryBase + { + public override string Name { get { return "Player"; } } + + public Player player; + public string userID; + public PlayerNet playerNet; + public TMPro.TextMeshProUGUI perfText; + + public override void Init(object[] parameters) + { + player = (Player)parameters[0]; + userID = player.field_Private_APIUser_0.id; + playerNet = player.GetComponent(); + + gameObject.GetComponent().onClick.AddListener(new System.Action(() => OpenPlayerInQuickMenu(player))); + perfText = player.transform.Find("Player Nameplate/Canvas/Nameplate/Contents/Quick Stats/Performance Text").GetComponent(); + } + public override void ProcessText(object[] parameters) + { + if (player == null) // Sometimes ppl will desync causing the leave event to not call + { + PlayerListMod.playerEntries.Remove(userID); + PlayerListMod.entries.Remove(Identifier); + Object.DestroyImmediate(gameObject); + return; + } + if (Config.condensedText.Value && !Config.HasSomethingOff) + textComponent.text = textComponent.text.Replace(" ", ""); + + if (playerNet != null) + { + if (Config.pingToggle.Value) + { + ChangeEntry("pingcolor", GetPingColor(playerNet.prop_Int16_0)); + ChangeEntry("ping", playerNet.prop_Int16_0.ToString().PadRight(4)); + } + + // I STG if I have to remove fps because skids start walking up to people saying poeple's fps im gonna murder someone + if (Config.fpsToggle.Value) + { + ChangeEntry("fpscolor", GetFpsColor((int)(1000f / playerNet.field_Private_Byte_0))); + if (playerNet.field_Private_Byte_0 == 0) + ChangeEntry("fps", "?".PadRight(3)); + else + ChangeEntry("fps", ((int)(1000f / playerNet.field_Private_Byte_0)).ToString().PadRight(3)); + } + } + else + { + if (Config.pingToggle.Value) + { + ChangeEntry("pingcolor", "#ff0000"); + ChangeEntry("ping", "?".PadRight(4)); + } + + + // I STG if I have to remove fps because skids start walking up to people saying poeple's fps im gonna murder someone + if (Config.fpsToggle.Value) + { + ChangeEntry("fpscolor", "#ff0000"); + ChangeEntry("fps", "?".PadRight(3)); + } + + playerNet = player.GetComponent(); + } + + if (Config.platformToggle.Value) + ChangeEntry("platform", ParsePlatform(player)); + + if (Config.perfToggle.Value) + { + if (perfText != null) + { + ChangeEntry("perfcolor", "#" + ColorUtility.ToHtmlStringRGB(perfText.color)); + ChangeEntry("perf", ParsePerformanceText(perfText.text).PadRight(5)); + } + else + { + perfText = player.transform.Find("Player Nameplate/Canvas/Nameplate/Contents/Quick Stats/Performance Text").GetComponent(); + ChangeEntry("perfcolor", "#ff00000"); + ChangeEntry("perf", "???"); + } + } + + if (Config.displayNameToggle.Value) // Why? + { + ChangeEntry("rankcolor", "#" + ColorUtility.ToHtmlStringRGB(VRCPlayer.Method_Public_Static_Color_APIUser_0(player.field_Private_APIUser_0))); + ChangeEntry("displayname", player.field_Private_APIUser_0.displayName); + } + + if (Config.HasSomethingOff) + textComponent.text = " - " + RemoveOffToggles(textComponent.text.Substring(3)); + } + + public static string ParsePlatform(Player player) + { + if (player.field_Private_APIUser_0.last_platform == "standalonewindows") + if (player.field_Private_VRCPlayerApi_0.IsUserInVR()) + return "VR".PadRight(5); + else + return "PC".PadRight(5); + else + return "Quest".PadRight(5); + } + + public static void OpenPlayerInQuickMenu(Player player) + { + if (InputManager.mouseCursor.gameObject.activeSelf) InputManager.mouseCursor.Method_Public_Void_VRCPlayer_0(player.field_Internal_VRCPlayer_0); + + if (InputManager.rightCursor.gameObject.activeSelf) InputManager.rightCursor.Method_Public_Void_VRCPlayer_0(player.field_Internal_VRCPlayer_0); + + if (InputManager.leftCursor.gameObject.activeSelf) InputManager.leftCursor.Method_Public_Void_VRCPlayer_0(player.field_Internal_VRCPlayer_0); + + QuickMenuContextualDisplay.Method_Public_Static_Void_VRCPlayer_0(player.field_Internal_VRCPlayer_0); + } + + public static string GetPingColor(int ping) + { + if (ping <= 75) + return "#00ff00"; + else if (ping > 75 && ping <= 125) + return "#008000"; + else if (ping > 125 && ping <= 175) + return "#ffff00"; + else if (ping > 175 && ping <= 225) + return "#ffa500"; + else + return "#ff0000"; + } + public static string GetFpsColor(int fps) + { + if (fps >= 60) + return "#00ff00"; + else if (fps < 60 && fps >= 45) + return "#008000"; + else if (fps < 45 && fps >= 30) + return "#ffff00"; + else if (fps < 30 && fps >= 15) + return "#ffa500"; + else + return "#ff0000"; + } + public static string ParsePerformanceText(string perfText) + { + switch (perfText.ToLower()) + { + case "very poor": + return "Awful"; + case "poor": + return "Poor"; + case "medium": + return "Med"; + case "good": + return "Good"; + case "excellent": + return "Great"; + default: + return perfText; + } + } + public static string RemoveOffToggles(string originalString) + { + int totalRemoved = 0; + List splitText; + splitText = originalString.Split(new string[] { " | " }, System.StringSplitOptions.None).ToList(); + + if (!Config.pingToggle.Value) + { + splitText.RemoveAt(0); + totalRemoved++; + } + if (!Config.fpsToggle.Value) + { + splitText.RemoveAt(1 - totalRemoved); + totalRemoved++; + } + if (!Config.platformToggle.Value) + { + splitText.RemoveAt(2 - totalRemoved); + totalRemoved++; + } + if (!Config.perfToggle.Value) + { + splitText.RemoveAt(3 - totalRemoved); + totalRemoved++; + } + if (!Config.displayNameToggle.Value) + { + splitText.RemoveAt(4 - totalRemoved); + } + + string finalString = ""; + for (int i = 0; i < splitText.Count; i++) + { + finalString += splitText[i]; + if (i + 1 == splitText.Count) continue; + + if (Config.condensedText.Value) + finalString += "|"; + else + finalString += " | "; + } + + return finalString; + } + } +} diff --git a/PlayerList/Entries/PlayerListHeaderEntry.cs b/PlayerList/Entries/PlayerListHeaderEntry.cs new file mode 100644 index 0000000..58a93c3 --- /dev/null +++ b/PlayerList/Entries/PlayerListHeaderEntry.cs @@ -0,0 +1,9 @@ +namespace PlayerList.Entries +{ + class PlayerListHeaderEntry : EntryBase + { + public override string Name { get { return "PlayerList Header"; } } + + public override void ProcessText(object[] parameters = null) => ChangeEntry("playercount", PlayerListMod.playerEntries.Count + 1); + } +} diff --git a/PlayerList/Entries/RoomTimeEntry.cs b/PlayerList/Entries/RoomTimeEntry.cs new file mode 100644 index 0000000..9a713df --- /dev/null +++ b/PlayerList/Entries/RoomTimeEntry.cs @@ -0,0 +1,15 @@ +using System; + +namespace PlayerList.Entries +{ + class RoomTimeEntry : EntryBase + { + public override string Name { get { return "Room Time"; } } + + public override void ProcessText(object[] parameters = null) + { + TimeSpan time = TimeSpan.FromSeconds(RoomManager.prop_Single_0); + ChangeEntry("roomtime", time.ToString(@"hh\:mm\:ss")); + } + } +} diff --git a/PlayerList/Entries/SystemTime12HrEntry.cs b/PlayerList/Entries/SystemTime12HrEntry.cs new file mode 100644 index 0000000..c65946a --- /dev/null +++ b/PlayerList/Entries/SystemTime12HrEntry.cs @@ -0,0 +1,12 @@ +using System; +using System.Globalization; + +namespace PlayerList.Entries +{ + class SystemTime12HrEntry : EntryBase + { + public override string Name { get { return "System Time 12Hr"; } } + + public override void ProcessText(object[] parameters = null) => ChangeEntry("systemtime12hr", DateTime.Now.ToString(@"hh\:mm\:ss tt", CultureInfo.InvariantCulture)); + } +} diff --git a/PlayerList/Entries/SystemTime24HrEntry.cs b/PlayerList/Entries/SystemTime24HrEntry.cs new file mode 100644 index 0000000..32cabd7 --- /dev/null +++ b/PlayerList/Entries/SystemTime24HrEntry.cs @@ -0,0 +1,11 @@ +using System; + +namespace PlayerList.Entries +{ + class SystemTime24HrEntry : EntryBase + { + public override string Name { get { return "System Time 24Hr"; } } + + public override void ProcessText(object[] parameters = null) => ChangeEntry("systemtime24hr", DateTime.Now.ToString(@"HH\:mm\:ss")); + } +} \ No newline at end of file diff --git a/PlayerList/Entries/WorldAuthorEntry.cs b/PlayerList/Entries/WorldAuthorEntry.cs new file mode 100644 index 0000000..176e32f --- /dev/null +++ b/PlayerList/Entries/WorldAuthorEntry.cs @@ -0,0 +1,9 @@ +namespace PlayerList.Entries +{ + class WorldAuthorEntry : EntryBase + { + public override string Name { get { return "World Author"; } } + + public override void ProcessText(object[] parameters = null) => ChangeEntry("worldauthor", RoomManager.field_Internal_Static_ApiWorld_0.authorName); + } +} \ No newline at end of file diff --git a/PlayerList/Entries/WorldNameEntry.cs b/PlayerList/Entries/WorldNameEntry.cs new file mode 100644 index 0000000..34b4adc --- /dev/null +++ b/PlayerList/Entries/WorldNameEntry.cs @@ -0,0 +1,9 @@ +namespace PlayerList.Entries +{ + class WorldNameEntry : EntryBase + { + public override string Name { get { return "World Name"; } } + + public override void ProcessText(object[] parameters = null) => ChangeEntry("worldname", RoomManager.field_Internal_Static_ApiWorld_0.name); + } +} diff --git a/PlayerList/InputManager.cs b/PlayerList/InputManager.cs new file mode 100644 index 0000000..86d4fdc --- /dev/null +++ b/PlayerList/InputManager.cs @@ -0,0 +1,41 @@ +using UnityEngine; + +namespace PlayerList +{ + class InputManager + { + public static bool IsUseInputPressed + { + get + { + if (mouseCursor.gameObject.active && mouseCursor.field_Private_VRCInput_0.field_Private_Boolean_0) return true; + if (rightCursor.gameObject.active && rightCursor.field_Private_VRCInput_0.field_Private_Boolean_0) return true; + if (leftCursor.gameObject.active && leftCursor.field_Private_VRCInput_0.field_Private_Boolean_0) return true; + + return false; + } + } + public static Vector3 HitPosition + { + get + { + if (mouseCursor.gameObject.active) return mouseCursor.field_Public_Vector3_0; + if (rightCursor.gameObject.active) return rightCursor.field_Public_Vector3_0; + if (leftCursor.gameObject.active) return leftCursor.field_Public_Vector3_0; + + return Vector3.zero; + } + } + + public static VRCSpaceUiCursor mouseCursor; + public static VRCUiCursor rightCursor; + public static VRCUiCursor leftCursor; + + public static void UiInit() + { + mouseCursor = GameObject.Find("_Application/CursorManager/BlueFireballMouse").GetComponent(); + rightCursor = GameObject.Find("_Application/CursorManager/RightHandBeam").GetComponent(); + leftCursor = GameObject.Find("_Application/CursorManager/LeftHandBeam").GetComponent(); + } + } +} diff --git a/PlayerList/NetworkHooks.cs b/PlayerList/NetworkHooks.cs new file mode 100644 index 0000000..662ba0d --- /dev/null +++ b/PlayerList/NetworkHooks.cs @@ -0,0 +1,23 @@ +using System; +using VRC; + +namespace PlayerList +{ + // Literally copied from knah's JoinNotifier (https://github.com/knah/VRCMods/tree/master/JoinNotifier) + class NetworkHooks + { + public static event Action OnPlayerLeave; + public static event Action OnPlayerJoin; + + public static void NetworkInit() + { + if (NetworkManager.field_Internal_Static_NetworkManager_0 == null) return; + + var field0 = NetworkManager.field_Internal_Static_NetworkManager_0.field_Internal_VRCEventDelegate_1_Player_0; + var field1 = NetworkManager.field_Internal_Static_NetworkManager_0.field_Internal_VRCEventDelegate_1_Player_1; + + field0.field_Private_HashSet_1_UnityAction_1_T_0.Add(new Action((player) => OnPlayerJoin?.Invoke(player))); + field1.field_Private_HashSet_1_UnityAction_1_T_0.Add(new Action((player) => OnPlayerLeave?.Invoke(player))); + } + } +} diff --git a/PlayerList/PlayerList.csproj b/PlayerList/PlayerList.csproj new file mode 100644 index 0000000..cef2fb9 --- /dev/null +++ b/PlayerList/PlayerList.csproj @@ -0,0 +1,66 @@ + + + + net472 + PlayerList + PlayerList + + + + + + + + + + + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\Assembly-CSharp.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2Cppmscorlib.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2CppSystem.Core.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\MelonLoader.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\Photon-DotNet.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnhollowerBaseLib.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\Unity.TextMeshPro.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.AssetBundleModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCCore-Standalone.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCSDKBase.dll + + + + + + + + \ No newline at end of file diff --git a/PlayerList/PlayerListMod.cs b/PlayerList/PlayerListMod.cs new file mode 100644 index 0000000..61cab8f --- /dev/null +++ b/PlayerList/PlayerListMod.cs @@ -0,0 +1,542 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using MelonLoader; +using PlayerList.Components; +using PlayerList.Entries; +using PlayerList.UI; +using PlayerList.Utilities; +using UnhollowerRuntimeLib; +using UnityEngine; +using UnityEngine.UI; +using VRC; + +[assembly: MelonInfo(typeof(PlayerList.PlayerListMod), "PlayerList", "1.2.0", "loukylor", "https://github.com/loukylor/VRC-Mods")] +[assembly: MelonGame("VRChat", "VRChat")] + +namespace PlayerList +{ + public class PlayerListMod : MelonMod + { + public static List playerListMenus = new List(); + public static GameObject playerList; + public static GameObject playerListMenuButton; + public static RectTransform playerListRect; + public static Dictionary playerEntries = new Dictionary(); + public static Dictionary generalInfoEntries = new Dictionary(); + public static Dictionary entries = new Dictionary(); + public static VerticalLayoutGroup playerListLayout; + public static VerticalLayoutGroup generalInfoLayout; + + private static Vector2 quickMenuColliderSize; + + private static readonly Stopwatch timer = Stopwatch.StartNew(); + + private static LocalPlayerEntry localPlayerEntry = null; + + private static Label snapToGridSizeLabel; + private static int _snapToGridSize; + public static int SnapToGridSize + { + get { return _snapToGridSize; } + set + { + if (value <= 0) return; + + hasConfigChanged = true; + Config.snapToGridSize.Value = value; + snapToGridSizeLabel.textComponent.text = $"Snap Grid Size: {value}"; + _snapToGridSize = value; + } + } + + private static bool shouldMove = false; + private static bool hasConfigChanged = false; + private static bool shouldStayHidden; + private static Label fontSizeLabel; + private static int _fontSize; + public static int FontSize + { + get { return _fontSize; } + set + { + hasConfigChanged = true; + Config.fontSize.Value = value; + _fontSize = value; + fontSizeLabel.textComponent.text = $"Font Size: {value}"; + foreach (EntryBase entry in entries.Values) + entry.textComponent.fontSize = value; + RefreshLayout(); + } + } + + private static PlayerListButtonPosition _playerListMenuButtonPosition; + public static PlayerListButtonPosition PlayerListMenuButtonPosition + { + get { return _playerListMenuButtonPosition; } + set + { + if (value == (PlayerListButtonPosition)Config.playerListMenuButtonPosition.Value) return; + + switch (value) + { + case PlayerListButtonPosition.TopLeft: + playerListMenuButton.transform.localPosition = Converters.ConvertToUnityUnits(new Vector3(4, -1)); + playerListMenuButton.GetComponent().pivot = new Vector2(0, 0); + break; + case PlayerListButtonPosition.TopRight: + playerListMenuButton.transform.localPosition = Converters.ConvertToUnityUnits(new Vector3(2, -1)); + playerListMenuButton.GetComponent().pivot = new Vector2(1, 0); + break; + case PlayerListButtonPosition.BottomLeft: + playerListMenuButton.transform.localPosition = Converters.ConvertToUnityUnits(new Vector3(2, -1)); + playerListMenuButton.GetComponent().pivot = new Vector2(1, 1); + break; + case PlayerListButtonPosition.BottomRight: + playerListMenuButton.transform.localPosition = Converters.ConvertToUnityUnits(new Vector3(4, -1)); + playerListMenuButton.GetComponent().pivot = new Vector2(0, 1); + break; + default: + PlayerListMenuButtonPosition = PlayerListButtonPosition.TopLeft; + return; + } + _playerListMenuButtonPosition = value; + Config.playerListMenuButtonPosition.Value = (int)value; + hasConfigChanged = true; + } + } + public static ToggleButton menuToggleButton; + + public override void VRChat_OnUiManagerInit() + { + ClassInjector.RegisterTypeInIl2Cpp(); + Config.RegisterSettings(); + + // Initialize Constants util + Constants.UIInit(); + + // Initialize input manager + InputManager.UiInit(); + + quickMenuColliderSize = Constants.quickMenu.GetComponent().size; + + // Stolen from UIExpansionKit (https://github.com/knah/VRCMods/blob/master/UIExpansionKit) #Imnotaskidiswear + MelonLogger.Msg("Loading List UI..."); + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("PlayerList.playerlistmod.assetbundle")) + { + using (var memoryStream = new MemoryStream((int)stream.Length)) + { + stream.CopyTo(memoryStream); + AssetBundle assetBundle = AssetBundle.LoadFromMemory_Internal(memoryStream.ToArray(), 0); + assetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; + playerList = UnityEngine.Object.Instantiate(assetBundle.LoadAsset_Internal("Assets/Prefabs/PlayerListMod.prefab", Il2CppType.Of()).Cast(), Constants.quickMenu.transform); + playerListMenuButton = UnityEngine.Object.Instantiate(assetBundle.LoadAsset_Internal("Assets/Prefabs/PlayerListMenuButton.prefab", Il2CppType.Of()).Cast(), Constants.shortcutMenu.transform); + } + } + SetLayerRecursive(playerListMenuButton, 12); + playerListMenuButton.GetComponent