using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BoneLib.BoneMenu; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Marrow; using Il2CppTMPro; using LabFusion.Data; using LabFusion.Network; using LabFusion.Network.Serialization; using LabFusion.Player; using LabFusion.SDK.Metadata; using LabFusion.SDK.Modules; using LabFusion.UI.Popups; using LabFusion.Utilities; using LabRP; using LabRP.Economy; using LabRP.Network; using LabRP.UI; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("LabRP")] [assembly: AssemblyFileVersion("1.1.2")] [assembly: MelonInfo(typeof(Core), "LabRP", "1.1.2", "windudes98", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("1.1.2.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LabRP { public static class BuildInfo { public const string Name = "LabRP"; public const string Version = "1.1.2"; public const string Author = "windudes98"; } public class Core : MelonMod { private static bool _fusionActive; public static Core Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; LabRPConfig.Initialize(); bool flag = MelonBase.FindMelon("LabFusion", "Lakatrazz") != null; bool flag2 = MelonBase.FindMelon("BoneLib", "The BONELAB Modding Community") != null; if (!flag || !flag2) { ((MelonBase)this).LoggerInstance.Error($"LabRP requires Fusion (installed: {flag}) and BoneLib (installed: {flag2})! " + "Install the missing mod(s) and restart the game."); return; } try { LoadFusionSide(); _fusionActive = true; ((MelonBase)this).LoggerInstance.Msg("LabRP initialized. Join or host a Fusion server to use the economy."); } catch (Exception value) { ((MelonBase)this).LoggerInstance.Error($"LabRP failed to initialize. Are Fusion and BoneLib up to date? {value}"); } } public override void OnUpdate() { if (_fusionActive) { FusionUpdate(); } } public override void OnApplicationQuit() { if (_fusionActive) { FusionQuit(); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void LoadFusionSide() { ModuleManager.RegisterModule(); } [MethodImpl(MethodImplOptions.NoInlining)] private static void FusionUpdate() { ServerEconomy.OnUpdate(); WristHud.OnUpdate(); LabRPMenu.Tick(); } [MethodImpl(MethodImplOptions.NoInlining)] private static void FusionQuit() { ServerEconomy.SaveIfHosting(); } } public static class LabRPConfig { public const long MaxBalance = 999999999999L; public static MelonPreferences_Category Category { get; private set; } public static MelonPreferences_Entry StartingBalance { get; private set; } public static MelonPreferences_Entry SalaryAmount { get; private set; } public static MelonPreferences_Entry SalaryIntervalMinutes { get; private set; } public static MelonPreferences_Entry HudEnabled { get; private set; } public static MelonPreferences_Entry HudBillboard { get; private set; } public static MelonPreferences_Entry HudScale { get; private set; } public static MelonPreferences_Entry HudOffsetX { get; private set; } public static MelonPreferences_Entry HudOffsetY { get; private set; } public static MelonPreferences_Entry HudOffsetZ { get; private set; } public static MelonPreferences_Entry HudYaw { get; private set; } public static void Initialize() { Category = MelonPreferences.CreateCategory("LabRP"); StartingBalance = Category.CreateEntry("StartingBalance", 500L, "Starting Balance", "Money a player starts with the first time they join your server.", false, false, (ValueValidator)null, (string)null); SalaryAmount = Category.CreateEntry("SalaryAmount", 200L, "Salary Amount", "Money paid to every connected player each paycheck.", false, false, (ValueValidator)null, (string)null); SalaryIntervalMinutes = Category.CreateEntry("SalaryIntervalMinutes", 5f, "Salary Interval (Minutes)", "How often paychecks are handed out.", false, false, (ValueValidator)null, (string)null); HudEnabled = Category.CreateEntry("HudEnabled", true, "Wrist HUD Enabled", (string)null, false, false, (ValueValidator)null, (string)null); HudBillboard = Category.CreateEntry("HudBillboard", true, "Wrist HUD Faces You", "When true the money counter always turns to face your head.", false, false, (ValueValidator)null, (string)null); HudScale = Category.CreateEntry("HudScale", 1f, "Wrist HUD Scale", (string)null, false, false, (ValueValidator)null, (string)null); HudOffsetX = Category.CreateEntry("HudOffsetX", 0f, "Wrist HUD Offset X", (string)null, false, false, (ValueValidator)null, (string)null); HudOffsetY = Category.CreateEntry("HudOffsetY", 0.055f, "Wrist HUD Offset Y", (string)null, false, false, (ValueValidator)null, (string)null); HudOffsetZ = Category.CreateEntry("HudOffsetZ", -0.085f, "Wrist HUD Offset Z", (string)null, false, false, (ValueValidator)null, (string)null); HudYaw = Category.CreateEntry("HudYaw", 0f, "Wrist HUD Yaw (Fixed Mode)", "Extra rotation applied when the HUD is not in billboard mode.", false, false, (ValueValidator)null, (string)null); SanitizeFloats(); } private static void SanitizeFloats() { if ((0u | (FixNonFinite(SalaryIntervalMinutes, 5f) ? 1u : 0u) | (FixNonFinite(HudScale, 1f) ? 1u : 0u) | (FixNonFinite(HudOffsetX, 0f) ? 1u : 0u) | (FixNonFinite(HudOffsetY, 0.055f) ? 1u : 0u) | (FixNonFinite(HudOffsetZ, -0.085f) ? 1u : 0u) | (FixNonFinite(HudYaw, 0f) ? 1u : 0u)) != 0) { Save(); } } private static bool FixNonFinite(MelonPreferences_Entry entry, float fallback) { if (float.IsNaN(entry.Value) || float.IsInfinity(entry.Value)) { entry.Value = fallback; return true; } return false; } public static void Save() { Category.SaveToFile(false); } public static long ClampBalance(long value) { if (value < 0) { return 0L; } if (value > 999999999999L) { return 999999999999L; } return value; } } public static class NameUtil { public const int MaxLength = 32; public static string Clean(string name) { if (string.IsNullOrWhiteSpace(name)) { return ""; } StringBuilder stringBuilder = new StringBuilder(name.Length); foreach (char c in name) { if (c != '<' && c != '>' && !char.IsControl(c)) { stringBuilder.Append(c); if (stringBuilder.Length >= 32) { break; } } } return stringBuilder.ToString().Trim(); } } } namespace LabRP.UI { public static class LabRPMenu { private static Page _rootPage; private static FunctionElement _balanceLabel; private static Page _payPage; private static Page _payDetailPage; private static Page _balancesPage; private static Page _adminPage; private static Page _adminDetailPage; private static Page _hudPage; private static ulong _payTarget; private static int _payAmount = 100; private static ulong _adminTarget; private static int _adminSetAmount = 500; private static bool _labelDirty; private static bool _pageDirty; private static float _rebuildCooldown; private const float RebuildIntervalSeconds = 0.3f; private static readonly Color Green = new Color(0.45f, 1f, 0.6f, 1f); private static readonly Color Gold = new Color(1f, 0.85f, 0.4f, 1f); private static readonly Color Cyan = new Color(0.5f, 0.9f, 1f, 1f); private static readonly Color Red = new Color(1f, 0.45f, 0.45f, 1f); public static void Setup() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) Menu.Initialize(); _rootPage = Page.Root.CreatePage("LabRP", Green, 0, true); _balanceLabel = _rootPage.CreateFunction("My Balance: $0", Color.white, (Action)delegate { }); _payPage = _rootPage.CreatePage("Pay Players", Cyan, 0, true); _payDetailPage = _payPage.CreatePage("Payment", Cyan, 0, false); _balancesPage = _rootPage.CreatePage("Balances", Green, 0, true); _adminPage = _rootPage.CreatePage("Host Admin", Gold, 0, true); _adminDetailPage = _adminPage.CreatePage("Manage Player", Gold, 0, false); _hudPage = _rootPage.CreatePage("Wrist HUD", Color.white, 0, true); BuildHudPage(); Menu.OnPageOpened += OnPageOpened; ClientEconomy.OnCacheChanged += OnCacheChanged; UpdateBalanceLabel(); } private static void OnPageOpened(Page page) { if (page == _payPage) { BuildPayPage(); } else if (page == _balancesPage) { BuildBalancesPage(); } else if (page == _adminPage) { BuildAdminPage(); } } private static void OnCacheChanged() { _labelDirty = true; _pageDirty = true; } public static void OnRosterChanged() { _pageDirty = true; } public static void Tick() { if (_rebuildCooldown > 0f) { _rebuildCooldown -= Time.unscaledDeltaTime; } else if (_labelDirty || _pageDirty) { _rebuildCooldown = 0.3f; if (_labelDirty) { _labelDirty = false; UpdateBalanceLabel(); } if (_pageDirty) { _pageDirty = false; RefreshCurrentPage(); } } } private static void RefreshCurrentPage() { Page currentPage = Menu.CurrentPage; if (currentPage == _payPage) { BuildPayPage(); } else if (currentPage == _adminPage) { BuildAdminPage(); } else if (currentPage == _balancesPage) { BuildBalancesPage(); } else if (currentPage == _payDetailPage) { if (!IsOnline(_payTarget)) { Menu.OpenPage(_payPage); } else { BuildPayDetailPage(); } } else if (currentPage == _adminDetailPage) { if (!IsOnline(_adminTarget)) { Menu.OpenPage(_adminPage); } else { BuildAdminDetailPage(); } } } private static bool IsOnline(ulong platformID) { PlayerID playerID = PlayerIDManager.GetPlayerID(platformID); if (playerID != null) { return playerID.IsValid; } return false; } private static void UpdateBalanceLabel() { if (_balanceLabel != null) { ((Element)_balanceLabel).ElementName = (NetworkInfo.HasServer ? $"My Balance: ${ClientEconomy.LocalBalance:N0}" : "My Balance: (join a Fusion lobby)"); } } private static void BuildPayPage() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) _payPage.RemoveAll(); if (!NetworkInfo.HasServer) { _payPage.CreateFunction("Join a Fusion lobby first!", Color.gray, (Action)delegate { }); return; } bool flag = false; foreach (PlayerID otherPlayer in GetOtherPlayers()) { flag = true; ulong platformID = otherPlayer.PlatformID; string displayName = ClientEconomy.GetDisplayName(platformID); _payPage.CreateFunction("Pay " + displayName, Cyan, (Action)delegate { _payTarget = platformID; BuildPayDetailPage(); Menu.OpenPage(_payDetailPage); }); } if (!flag) { _payPage.CreateFunction("Nobody else is here yet...", Color.gray, (Action)delegate { }); } } private static void BuildPayDetailPage() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) _payDetailPage.RemoveAll(); _payAmount = Math.Clamp(_payAmount, 1, 1000000); string displayName = ClientEconomy.GetDisplayName(_payTarget); _payDetailPage.CreateFunction("Paying: " + displayName, Color.white, (Action)delegate { }); _payDetailPage.CreateFunction($"Your money: ${ClientEconomy.LocalBalance:N0}", Color.gray, (Action)delegate { }); _payDetailPage.CreateInt("Amount", Cyan, _payAmount, 50, 1, 1000000, (Action)delegate(int value) { _payAmount = value; }); _payDetailPage.CreateFunction("+ $100", Color.white, (Action)delegate { _payAmount += 100; BuildPayDetailPage(); }); _payDetailPage.CreateFunction("+ $1,000", Color.white, (Action)delegate { _payAmount += 1000; BuildPayDetailPage(); }); _payDetailPage.CreateFunction("Reset Amount", Color.gray, (Action)delegate { _payAmount = 100; BuildPayDetailPage(); }); _payDetailPage.CreateFunction("CONFIRM PAYMENT", Green, (Action)delegate { SendPayRequest(_payTarget, _payAmount); Menu.OpenPage(_payPage); }); } private static void SendPayRequest(ulong targetPlatformID, long amount) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (NetworkInfo.HasServer && amount > 0) { MessageRelay.RelayModule(new PayRequestData { TargetPlatformID = targetPlatformID, Amount = amount }, new MessageRoute((RelayType)1, (NetworkChannel)0)); } } private static void BuildBalancesPage() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) _balancesPage.RemoveAll(); if (!NetworkInfo.HasServer) { _balancesPage.CreateFunction("Join a Fusion lobby first!", Color.gray, (Action)delegate { }); return; } List> leaderboard = ClientEconomy.GetLeaderboard(); if (leaderboard.Count == 0) { _balancesPage.CreateFunction("No balances synced yet...", Color.gray, (Action)delegate { }); return; } ulong localPlatformID = PlayerIDManager.LocalPlatformID; int num = 1; foreach (KeyValuePair item in leaderboard) { string displayName = ClientEconomy.GetDisplayName(item.Key); bool num2 = item.Key == localPlatformID; Color val = (num2 ? Green : Color.white); string value = (num2 ? " (you)" : ""); _balancesPage.CreateFunction($"#{num} {displayName}{value}: ${item.Value.Balance:N0}", val, (Action)delegate { }); num++; } } private static void BuildAdminPage() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) _adminPage.RemoveAll(); if (!NetworkInfo.HasServer) { _adminPage.CreateFunction("Join a Fusion lobby first!", Color.gray, (Action)delegate { }); return; } if (!NetworkInfo.IsHost) { _adminPage.CreateFunction("Host only. You are not the host.", Color.gray, (Action)delegate { }); return; } _adminPage.CreateFunction("Salary Settings", Gold, (Action)delegate { }); _adminPage.CreateInt("Salary Amount", Color.white, (int)Math.Clamp(LabRPConfig.SalaryAmount.Value, 0L, 1000000L), 50, 0, 1000000, (Action)delegate(int value) { LabRPConfig.SalaryAmount.Value = value; LabRPConfig.Save(); }); _adminPage.CreateFloat("Salary Interval (min)", Color.white, Mathf.Clamp(LabRPConfig.SalaryIntervalMinutes.Value, 0.5f, 120f), 0.5f, 0.5f, 120f, (Action)delegate(float value) { LabRPConfig.SalaryIntervalMinutes.Value = value; LabRPConfig.Save(); }); _adminPage.CreateFunction("Pay Salary To Everyone NOW", Green, (Action)ServerEconomy.PayEveryoneSalary); _adminPage.CreateFunction("Players", Gold, (Action)delegate { }); foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid) { ulong platformID = playerID.PlatformID; string text = ClientEconomy.GetDisplayName(platformID); if (playerID.IsMe) { text += " (you)"; } _adminPage.CreateFunction("Manage " + text, Color.white, (Action)delegate { _adminTarget = platformID; BuildAdminDetailPage(); Menu.OpenPage(_adminDetailPage); }); } } _adminPage.CreateFunction("RESET ALL BALANCES", Red, (Action)delegate { Menu.DisplayDialog("Reset Economy", "Reset every player to the starting balance and wipe all saved records?", (Texture2D)null, (Action)ServerEconomy.ResetAllBalances, (Action)null); }); } private static void BuildAdminDetailPage() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) _adminDetailPage.RemoveAll(); string displayName = ClientEconomy.GetDisplayName(_adminTarget); long balance = ServerEconomy.GetBalance(_adminTarget); _adminDetailPage.CreateFunction($"{displayName}: ${balance:N0}", Color.white, (Action)delegate { }); _adminDetailPage.CreateFunction("Give $100", Green, (Action)delegate { AdminChange(100L, give: true); }); _adminDetailPage.CreateFunction("Give $1,000", Green, (Action)delegate { AdminChange(1000L, give: true); }); _adminDetailPage.CreateFunction("Give $10,000", Green, (Action)delegate { AdminChange(10000L, give: true); }); _adminDetailPage.CreateFunction("Take $100", Red, (Action)delegate { AdminChange(100L, give: false); }); _adminDetailPage.CreateFunction("Take $1,000", Red, (Action)delegate { AdminChange(1000L, give: false); }); _adminDetailPage.CreateInt("Set Amount", Color.white, _adminSetAmount, 100, 0, 1000000, (Action)delegate(int value) { _adminSetAmount = value; }); _adminDetailPage.CreateFunction("Apply 'Set Amount'", Gold, (Action)delegate { ServerEconomy.AdminSet(_adminTarget, _adminSetAmount); BuildAdminDetailPage(); }); } private static void AdminChange(long amount, bool give) { if (give) { ServerEconomy.AdminGive(_adminTarget, amount); } else { ServerEconomy.AdminTake(_adminTarget, amount); } BuildAdminDetailPage(); } private static void BuildHudPage() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) _hudPage.RemoveAll(); _hudPage.CreateBool("HUD Enabled", Color.white, LabRPConfig.HudEnabled.Value, (Action)delegate(bool value) { LabRPConfig.HudEnabled.Value = value; LabRPConfig.Save(); }); _hudPage.CreateBool("Always Face You", Color.white, LabRPConfig.HudBillboard.Value, (Action)delegate(bool value) { LabRPConfig.HudBillboard.Value = value; LabRPConfig.Save(); WristHud.ApplyTransformPrefs(); }); _hudPage.CreateFloat("Scale", Color.white, LabRPConfig.HudScale.Value, 0.25f, 0.1f, 6f, (Action)delegate(float value) { LabRPConfig.HudScale.Value = value; WristHud.ApplyTransformPrefs(); }); _hudPage.CreateFunction("Position (1cm steps)", Color.gray, (Action)delegate { }); CreateNudge("Up", 0f, 0.01f, 0f); CreateNudge("Down", 0f, -0.01f, 0f); CreateNudge("Left", -0.01f, 0f, 0f); CreateNudge("Right", 0.01f, 0f, 0f); CreateNudge("Toward Elbow", 0f, 0f, -0.01f); CreateNudge("Toward Fingers", 0f, 0f, 0.01f); _hudPage.CreateFloat("Yaw (fixed mode)", Color.white, LabRPConfig.HudYaw.Value, 15f, -180f, 180f, (Action)delegate(float value) { LabRPConfig.HudYaw.Value = value; WristHud.ApplyTransformPrefs(); }); _hudPage.CreateFunction("Save HUD Settings", Green, (Action)LabRPConfig.Save); _hudPage.CreateFunction("Reset HUD Defaults", Red, (Action)delegate { LabRPConfig.HudEnabled.Value = true; LabRPConfig.HudBillboard.Value = true; LabRPConfig.HudScale.Value = 1f; LabRPConfig.HudOffsetX.Value = 0f; LabRPConfig.HudOffsetY.Value = 0.055f; LabRPConfig.HudOffsetZ.Value = -0.085f; LabRPConfig.HudYaw.Value = 0f; LabRPConfig.Save(); WristHud.ApplyTransformPrefs(); BuildHudPage(); }); } private static void CreateNudge(string label, float dx, float dy, float dz) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) _hudPage.CreateFunction("Nudge " + label, Color.white, (Action)delegate { MelonPreferences_Entry hudOffsetX = LabRPConfig.HudOffsetX; hudOffsetX.Value += dx; MelonPreferences_Entry hudOffsetY = LabRPConfig.HudOffsetY; hudOffsetY.Value += dy; MelonPreferences_Entry hudOffsetZ = LabRPConfig.HudOffsetZ; hudOffsetZ.Value += dz; WristHud.ApplyTransformPrefs(); }); } private static IEnumerable GetOtherPlayers() { foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid && !playerID.IsMe) { yield return playerID; } } } } public static class WristHud { private static GameObject _root; private static Transform _rootTransform; private static TextMeshPro _balanceText; private static TextMeshPro _popupText; private static TMP_FontAsset _font; private static long _targetBalance = 0L; private static double _displayedBalance = 0.0; private static long _lastDrawnBalance = -1L; private static float _popupTimer = 0f; private const float PopupDuration = 3.5f; private static readonly Color MoneyGreen = new Color(0.45f, 1f, 0.6f, 1f); private static readonly Color SoftRed = new Color(1f, 0.42f, 0.42f, 1f); private static readonly Color HostGold = new Color(1f, 0.85f, 0.4f, 1f); public static void OnLocalRigCreated() { _root = null; _rootTransform = null; _balanceText = null; _popupText = null; TryBuild(); } public static void OnJoinedLobby() { _targetBalance = ClientEconomy.LocalBalance; _displayedBalance = _targetBalance; _popupTimer = 0f; TryBuild(); } public static void OnLeftLobby() { _targetBalance = 0L; _displayedBalance = 0.0; DestroyHud(); } public static void OnUpdate() { if (!NetworkInfo.HasServer) { if ((Object)(object)_root != (Object)null) { DestroyHud(); } return; } if (!LabRPConfig.HudEnabled.Value) { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } return; } if ((Object)(object)_root != (Object)null && ((Object)(object)_balanceText == (Object)null || (Object)(object)_popupText == (Object)null)) { DestroyHud(); } if ((Object)(object)_root == (Object)null) { TryBuild(); if ((Object)(object)_root == (Object)null) { return; } } if (!_root.activeSelf) { _root.SetActive(true); } AnimateBalance(); AnimatePopup(); Billboard(); } public static void OnLocalBalanceChanged(long newBalance, long delta, BalanceReason reason, string detail) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) _targetBalance = newBalance; detail = NameUtil.Clean(detail); switch (reason) { case BalanceReason.Initial: _displayedBalance = newBalance; return; case BalanceReason.Denied: ShowPopup(detail, SoftRed); return; } if (delta != 0L) { switch (reason) { case BalanceReason.Paycheck: ShowPopup("+$" + FormatAbs(delta) + " Paycheck", MoneyGreen); break; case BalanceReason.PaymentReceived: ShowPopup("+$" + FormatAbs(delta) + " from " + detail, MoneyGreen); break; case BalanceReason.PaymentSent: ShowPopup("-$" + FormatAbs(delta) + " to " + detail, SoftRed); break; case BalanceReason.Admin: ShowPopup((delta >= 0) ? ("+$" + FormatAbs(delta) + " Host") : ("-$" + FormatAbs(delta) + " Host"), HostGold); break; } } } private static string FormatAbs(long value) { if (value == long.MinValue) { value = long.MaxValue; } return Math.Abs(value).ToString("N0"); } private static void TryBuild() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && NetworkInfo.HasServer && LabRPConfig.HudEnabled.Value && RigData.HasPlayer) { Hand leftHand = RigData.Refs.LeftHand; if (!((Object)(object)leftHand == (Object)null)) { TMP_FontAsset font = GetFont(); _root = new GameObject("LabRP_WristHUD"); _rootTransform = _root.transform; _rootTransform.SetParent(((Component)leftHand).transform, false); ApplyTransformPrefs(); _balanceText = CreateText("BalanceText", font, 30f, MoneyGreen, (FontStyles)1); _balanceText.transform.localPosition = Vector3.zero; _popupText = CreateText("PopupText", font, 15f, MoneyGreen, (FontStyles)0); _popupText.transform.localPosition = new Vector3(0f, -3f, 0f); SetAlpha((TMP_Text)(object)_popupText, 0f); _displayedBalance = _targetBalance; _lastDrawnBalance = -1L; DrawBalance((long)_displayedBalance); } } } private static TextMeshPro CreateText(string name, TMP_FontAsset font, float fontSize, Color color, FontStyles style) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(_root.transform, false); val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one; TextMeshPro val2 = val.AddComponent(); if ((Object)(object)font != (Object)null) { ((TMP_Text)val2).font = font; } ((TMP_Text)val2).fontSize = fontSize; ((TMP_Text)val2).fontStyle = style; ((Graphic)val2).color = color; ((TMP_Text)val2).rectTransform.sizeDelta = new Vector2(40f, 10f); ((TMP_Text)val2).enableWordWrapping = false; ((TMP_Text)val2).text = ""; ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; return val2; } public static void ApplyTransformPrefs() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_rootTransform == (Object)null)) { _rootTransform.localPosition = new Vector3(LabRPConfig.HudOffsetX.Value, LabRPConfig.HudOffsetY.Value, LabRPConfig.HudOffsetZ.Value); _rootTransform.localRotation = Quaternion.Euler(0f, LabRPConfig.HudYaw.Value, 0f); float num = 0.01f * Mathf.Clamp(LabRPConfig.HudScale.Value, 0.1f, 6f); _rootTransform.localScale = new Vector3(num, num, num); } } private static void DestroyHud() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _rootTransform = null; _balanceText = null; _popupText = null; _popupTimer = 0f; } private static void AnimateBalance() { if ((Object)(object)_balanceText == (Object)null) { return; } double value = (double)_targetBalance - _displayedBalance; if (Math.Abs(value) < 0.5) { _displayedBalance = _targetBalance; } else { double num = Math.Max(Math.Abs(value) * 6.0, 40.0) * (double)Time.unscaledDeltaTime; if (num >= Math.Abs(value)) { _displayedBalance = _targetBalance; } else { _displayedBalance += (double)Math.Sign(value) * num; } } DrawBalance((long)Math.Round(_displayedBalance)); } private static void DrawBalance(long value) { if ((Object)(object)_balanceText != (Object)null && value != _lastDrawnBalance) { _lastDrawnBalance = value; ((TMP_Text)_balanceText).text = $"${value:N0}"; } } private static void ShowPopup(string message, Color color) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) _popupTimer = 3.5f; if (!((Object)(object)_popupText == (Object)null)) { ((TMP_Text)_popupText).text = message; ((Graphic)_popupText).color = new Color(color.r, color.g, color.b, 1f); } } private static void AnimatePopup() { if (!((Object)(object)_popupText == (Object)null) && !(_popupTimer <= 0f)) { _popupTimer -= Time.unscaledDeltaTime; float alpha = Mathf.Clamp01(_popupTimer / 1f); SetAlpha((TMP_Text)(object)_popupText, alpha); if (_popupTimer <= 0f) { ((TMP_Text)_popupText).text = ""; } } } private static void SetAlpha(TMP_Text text, float alpha) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) Color color = ((Graphic)text).color; color.a = alpha; ((Graphic)text).color = color; } private static void Billboard() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (!LabRPConfig.HudBillboard.Value || (Object)(object)_rootTransform == (Object)null) { return; } Transform val = (RigData.HasPlayer ? RigData.Refs.Head : null); if (!((Object)(object)val == (Object)null)) { Vector3 val2 = _rootTransform.position - val.position; if (!(((Vector3)(ref val2)).sqrMagnitude < 0.0001f)) { _rootTransform.rotation = Quaternion.LookRotation(((Vector3)(ref val2)).normalized, Vector3.up); } } } private static TMP_FontAsset GetFont() { if ((Object)(object)_font != (Object)null) { return _font; } try { Il2CppReferenceArray obj = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); TMP_FontAsset val = null; foreach (Object item in (Il2CppArrayBase)(object)obj) { TMP_FontAsset val2 = ((Il2CppObjectBase)item).TryCast(); if (!((Object)(object)val2 == (Object)null)) { if (val == null) { val = val2; } if (((Object)val2).name.IndexOf("arlon-medium", StringComparison.OrdinalIgnoreCase) >= 0) { _font = val2; return _font; } } } _font = val; if ((Object)(object)_font == (Object)null) { MelonLogger.Warning("[LabRP] No TMP font assets are loaded yet; HUD text may be invisible until the next rebuild."); } } catch (Exception ex) { MelonLogger.Warning("[LabRP] Could not find a TMP font, HUD text may be invisible: " + ex.Message); } return _font; } } } namespace LabRP.Network { public enum BalanceReason : byte { Initial, Paycheck, PaymentSent, PaymentReceived, Admin, Denied } public class PayRequestData : INetSerializable { public ulong TargetPlatformID; public long Amount; public int? GetSize() { return 16; } public void Serialize(INetSerializer serializer) { serializer.SerializeValue(ref TargetPlatformID); serializer.SerializeValue(ref Amount); } } public class PayRequestMessage : ModuleMessageHandler { protected override void OnHandleMessage(ReceivedMessage received) { if (((ReceivedMessage)(ref received)).IsServerHandled && NetworkInfo.IsHost && ((ReceivedMessage)(ref received)).Sender.HasValue) { PayRequestData payRequestData = ((ReceivedMessage)(ref received)).ReadData(); ServerEconomy.TryTransfer(((ReceivedMessage)(ref received)).Sender.Value, payRequestData.TargetPlatformID, payRequestData.Amount); } } } public class BalanceUpdateData : INetSerializable { public ulong PlatformID; public long NewBalance; public long Delta; public BalanceReason Reason; public string Detail = ""; public int? GetSize() { return 25 + SizeExtensions.GetSize(Detail); } public void Serialize(INetSerializer serializer) { serializer.SerializeValue(ref PlatformID); serializer.SerializeValue(ref NewBalance); serializer.SerializeValue(ref Delta); serializer.SerializeValue(ref Reason, (Precision)3); serializer.SerializeValue(ref Detail); } } public class BalanceUpdateMessage : ModuleMessageHandler { protected override void OnHandleMessage(ReceivedMessage received) { if (((ReceivedMessage)(ref received)).Sender.HasValue && ((ReceivedMessage)(ref received)).Sender.Value == 0) { ClientEconomy.ApplyUpdate(((ReceivedMessage)(ref received)).ReadData()); } } } public class SnapshotEntry { public ulong PlatformID; public string Name = ""; public long Balance; } public class EconomySnapshotData : INetSerializable { public List Entries = new List(); public const int MaxEntries = 512; public int? GetSize() { int num = 4; foreach (SnapshotEntry entry in Entries) { num += 8 + SizeExtensions.GetSize(entry.Name ?? "") + 8; } return num; } public void Serialize(INetSerializer serializer) { int num = Entries?.Count ?? 0; serializer.SerializeValue(ref num); if (serializer.IsReader) { if (num < 0 || num > 512) { Entries = new List(); return; } Entries = new List(num); for (int i = 0; i < num; i++) { ulong platformID = 0uL; string text = ""; long balance = 0L; serializer.SerializeValue(ref platformID); serializer.SerializeValue(ref text); serializer.SerializeValue(ref balance); Entries.Add(new SnapshotEntry { PlatformID = platformID, Name = (text ?? ""), Balance = balance }); } } else { for (int j = 0; j < num; j++) { SnapshotEntry snapshotEntry = Entries[j]; ulong platformID2 = snapshotEntry.PlatformID; string text2 = snapshotEntry.Name ?? ""; long balance2 = snapshotEntry.Balance; serializer.SerializeValue(ref platformID2); serializer.SerializeValue(ref text2); serializer.SerializeValue(ref balance2); } } } } public class EconomySnapshotMessage : ModuleMessageHandler { protected override void OnHandleMessage(ReceivedMessage received) { if (((ReceivedMessage)(ref received)).Sender.HasValue && ((ReceivedMessage)(ref received)).Sender.Value == 0) { ClientEconomy.ApplySnapshot(((ReceivedMessage)(ref received)).ReadData()); } } } public class LabRPModule : Module { [CompilerGenerated] private static class <>O { public static ServerEvent <0>__OnStartedServer; public static ServerEvent <1>__OnJoinedServer; public static ServerEvent <2>__OnDisconnected; public static PlayerUpdate <3>__OnPlayerJoined; public static PlayerUpdate <4>__OnPlayerLeft; public static Action <5>__OnMetadataChanged; public static Action <6>__OnLocalBalanceChanged; } private Action _onRigCreated; private Action _onLocalChanged; private Action _onMetadataChanged; public override string Name => "LabRP"; public override string Author => "windudes98"; public override Version Version => new Version(1, 1, 2); public override ConsoleColor Color => ConsoleColor.Green; protected override void OnModuleRegistered() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown ModuleMessageManager.LoadHandlers(Assembly.GetExecutingAssembly()); object obj = <>O.<0>__OnStartedServer; if (obj == null) { ServerEvent val = OnStartedServer; <>O.<0>__OnStartedServer = val; obj = (object)val; } MultiplayerHooking.OnStartedServer += (ServerEvent)obj; object obj2 = <>O.<1>__OnJoinedServer; if (obj2 == null) { ServerEvent val2 = OnJoinedServer; <>O.<1>__OnJoinedServer = val2; obj2 = (object)val2; } MultiplayerHooking.OnJoinedServer += (ServerEvent)obj2; object obj3 = <>O.<2>__OnDisconnected; if (obj3 == null) { ServerEvent val3 = OnDisconnected; <>O.<2>__OnDisconnected = val3; obj3 = (object)val3; } MultiplayerHooking.OnDisconnected += (ServerEvent)obj3; object obj4 = <>O.<3>__OnPlayerJoined; if (obj4 == null) { PlayerUpdate val4 = OnPlayerJoined; <>O.<3>__OnPlayerJoined = val4; obj4 = (object)val4; } MultiplayerHooking.OnPlayerJoined += (PlayerUpdate)obj4; object obj5 = <>O.<4>__OnPlayerLeft; if (obj5 == null) { PlayerUpdate val5 = OnPlayerLeft; <>O.<4>__OnPlayerLeft = val5; obj5 = (object)val5; } MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj5; _onMetadataChanged = OnMetadataChanged; PlayerID.OnMetadataChangedEvent += _onMetadataChanged; _onRigCreated = delegate { WristHud.OnLocalRigCreated(); }; LocalPlayer.OnLocalRigCreated = (Action)Delegate.Combine(LocalPlayer.OnLocalRigCreated, _onRigCreated); _onLocalChanged = OnLocalBalanceChanged; ClientEconomy.OnLocalChanged += _onLocalChanged; LabRPMenu.Setup(); ((Module)this).LoggerInstance.Log("LabRP module registered: economy online.", ConsoleColor.White); } protected override void OnModuleUnregistered() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown object obj = <>O.<0>__OnStartedServer; if (obj == null) { ServerEvent val = OnStartedServer; <>O.<0>__OnStartedServer = val; obj = (object)val; } MultiplayerHooking.OnStartedServer -= (ServerEvent)obj; object obj2 = <>O.<1>__OnJoinedServer; if (obj2 == null) { ServerEvent val2 = OnJoinedServer; <>O.<1>__OnJoinedServer = val2; obj2 = (object)val2; } MultiplayerHooking.OnJoinedServer -= (ServerEvent)obj2; object obj3 = <>O.<2>__OnDisconnected; if (obj3 == null) { ServerEvent val3 = OnDisconnected; <>O.<2>__OnDisconnected = val3; obj3 = (object)val3; } MultiplayerHooking.OnDisconnected -= (ServerEvent)obj3; object obj4 = <>O.<3>__OnPlayerJoined; if (obj4 == null) { PlayerUpdate val4 = OnPlayerJoined; <>O.<3>__OnPlayerJoined = val4; obj4 = (object)val4; } MultiplayerHooking.OnPlayerJoined -= (PlayerUpdate)obj4; object obj5 = <>O.<4>__OnPlayerLeft; if (obj5 == null) { PlayerUpdate val5 = OnPlayerLeft; <>O.<4>__OnPlayerLeft = val5; obj5 = (object)val5; } MultiplayerHooking.OnPlayerLeft -= (PlayerUpdate)obj5; PlayerID.OnMetadataChangedEvent -= _onMetadataChanged; LocalPlayer.OnLocalRigCreated = (Action)Delegate.Remove(LocalPlayer.OnLocalRigCreated, _onRigCreated); ClientEconomy.OnLocalChanged -= _onLocalChanged; } private static void OnStartedServer() { ServerEconomy.OnServerStarted(); WristHud.OnJoinedLobby(); } private static void OnJoinedServer() { WristHud.OnJoinedLobby(); } private static void OnDisconnected() { ServerEconomy.OnDisconnected(); ClientEconomy.Clear(); WristHud.OnLeftLobby(); } private static void OnPlayerJoined(PlayerID playerID) { ServerEconomy.OnPlayerJoined(playerID); LabRPMenu.OnRosterChanged(); } private static void OnPlayerLeft(PlayerID playerID) { ServerEconomy.OnPlayerLeft(playerID); LabRPMenu.OnRosterChanged(); } private static void OnMetadataChanged(PlayerID playerID, string key, string value) { if (key == "Username") { ServerEconomy.OnUsernameChanged(playerID); } } private static void OnLocalBalanceChanged(long newBalance, long delta, BalanceReason reason, string detail) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown WristHud.OnLocalBalanceChanged(newBalance, delta, reason, detail); detail = NameUtil.Clean(detail); switch (reason) { case BalanceReason.PaymentReceived: if (delta != 0L) { Notifier.Send(new Notification { Title = NotificationText.op_Implicit("LabRP"), Message = NotificationText.op_Implicit(detail + " paid you $" + FormatAbs(delta) + "!"), Type = (NotificationType)3, ShowPopup = true, PopupLength = 3f }); } break; case BalanceReason.Denied: Notifier.Send(new Notification { Title = NotificationText.op_Implicit("LabRP"), Message = NotificationText.op_Implicit("Payment failed: " + detail), Type = (NotificationType)1, ShowPopup = true, PopupLength = 3f }); break; case BalanceReason.Admin: if (delta != 0L) { Notifier.Send(new Notification { Title = NotificationText.op_Implicit("LabRP"), Message = NotificationText.op_Implicit((delta >= 0) ? ("The host gave you $" + FormatAbs(delta) + ".") : ("The host took $" + FormatAbs(delta) + " from you.")), Type = (NotificationType)0, ShowPopup = true, PopupLength = 3f }); } break; } } private static string FormatAbs(long value) { if (value == long.MinValue) { value = long.MaxValue; } return Math.Abs(value).ToString("N0"); } } } namespace LabRP.Economy { public static class ClientEconomy { public class CachedPlayer { public string Name = ""; public long Balance; } private static readonly Dictionary _cache = new Dictionary(); public static long LocalBalance { get { ulong localPlatformID = PlayerIDManager.LocalPlatformID; if (!_cache.TryGetValue(localPlatformID, out var value)) { return 0L; } return value.Balance; } } public static event Action OnCacheChanged; public static event Action OnLocalChanged; public static void ApplySnapshot(EconomySnapshotData data) { _cache.Clear(); foreach (SnapshotEntry entry in data.Entries) { _cache[entry.PlatformID] = new CachedPlayer { Name = NameUtil.Clean(entry.Name), Balance = entry.Balance }; } SafeInvokeCacheChanged(); SafeInvokeLocalChanged(LocalBalance, 0L, BalanceReason.Initial, ""); } public static void ApplyUpdate(BalanceUpdateData data) { bool flag = data.PlatformID == PlayerIDManager.LocalPlatformID; if (data.Reason == BalanceReason.Denied) { if (flag) { SafeInvokeLocalChanged(LocalBalance, 0L, BalanceReason.Denied, data.Detail ?? ""); } return; } if (!_cache.TryGetValue(data.PlatformID, out var value)) { value = new CachedPlayer(); _cache[data.PlatformID] = value; } value.Balance = data.NewBalance; if (string.IsNullOrEmpty(value.Name)) { value.Name = NameUtil.Clean(GetLiveName(data.PlatformID)); } SafeInvokeCacheChanged(); if (flag) { SafeInvokeLocalChanged(data.NewBalance, data.Delta, data.Reason, data.Detail ?? ""); } } public static void Clear() { _cache.Clear(); SafeInvokeCacheChanged(); } public static string GetDisplayName(ulong platformID) { string text = NameUtil.Clean(GetLiveName(platformID)); if (!string.IsNullOrWhiteSpace(text)) { return text; } if (_cache.TryGetValue(platformID, out var value) && !string.IsNullOrWhiteSpace(value.Name)) { return value.Name; } return "Player"; } public static long GetBalance(ulong platformID) { if (!_cache.TryGetValue(platformID, out var value)) { return 0L; } return value.Balance; } public static List> GetLeaderboard() { return _cache.OrderByDescending((KeyValuePair pair) => pair.Value.Balance).ToList(); } private static string GetLiveName(ulong platformID) { PlayerID playerID = PlayerIDManager.GetPlayerID(platformID); if (playerID == null) { return null; } PlayerMetadata metadata = playerID.Metadata; if (metadata == null) { return null; } MetadataVariable username = metadata.Username; if (username == null) { return null; } return username.GetValue(); } private static void SafeInvokeCacheChanged() { try { ClientEconomy.OnCacheChanged?.Invoke(); } catch (Exception value) { MelonLogger.Error($"[LabRP] Error in OnCacheChanged listener: {value}"); } } private static void SafeInvokeLocalChanged(long balance, long delta, BalanceReason reason, string detail) { try { ClientEconomy.OnLocalChanged?.Invoke(balance, delta, reason, detail); } catch (Exception value) { MelonLogger.Error($"[LabRP] Error in OnLocalChanged listener: {value}"); } } } public class PlayerRecord { public string Name = "Unknown"; public long Balance; public string LastSeenUtc = ""; public string FirstSeenUtc = ""; } public static class EconomySave { private static string FolderPath => Path.Combine(MelonEnvironment.UserDataDirectory, "LabRP"); private static string FilePath => Path.Combine(FolderPath, "economy.json"); private static string BackupPath => Path.Combine(FolderPath, "economy.backup.json"); private static string TempPath => Path.Combine(FolderPath, "economy.tmp.json"); private static string DamagedPath => Path.Combine(FolderPath, "economy.damaged.json"); public static Dictionary Load() { Dictionary dictionary = TryLoadFile(FilePath, "save"); bool flag = dictionary == null && File.Exists(FilePath); if (flag) { PreserveDamagedMain(); } bool flag2 = false; if (dictionary == null) { dictionary = TryLoadFile(BackupPath, "backup"); flag2 = dictionary != null; } if (dictionary == null) { if (flag) { MelonLogger.Error("[LabRP] Both save files were unreadable. The broken save was kept as economy.damaged.json; starting a fresh economy."); } return new Dictionary(); } Sanitize(dictionary); if (flag2) { Save(dictionary); MelonLogger.Msg("[LabRP] Recovered the economy from the backup file. The unreadable save was kept as economy.damaged.json."); } MelonLogger.Msg($"[LabRP] Loaded {dictionary.Count} saved player record(s)."); return dictionary; } private static Dictionary TryLoadFile(string path, string label) { try { if (!File.Exists(path)) { return null; } return JsonConvert.DeserializeObject>(File.ReadAllText(path)); } catch (Exception ex) { MelonLogger.Error("[LabRP] Failed to read the " + label + " file: " + ex.Message); return null; } } private static void PreserveDamagedMain() { try { if (File.Exists(FilePath)) { File.Copy(FilePath, DamagedPath, overwrite: true); } } catch { } } private static void Sanitize(Dictionary records) { foreach (ulong item in records.Keys.ToList()) { if (records[item] == null) { records[item] = new PlayerRecord { Name = "Unknown", Balance = LabRPConfig.ClampBalance(LabRPConfig.StartingBalance.Value), FirstSeenUtc = DateTime.UtcNow.ToString("o"), LastSeenUtc = DateTime.UtcNow.ToString("o") }; } } foreach (PlayerRecord value in records.Values) { value.Balance = LabRPConfig.ClampBalance(value.Balance); value.Name = (string.IsNullOrWhiteSpace(value.Name) ? "Unknown" : NameUtil.Clean(value.Name)); PlayerRecord playerRecord = value; if (playerRecord.LastSeenUtc == null) { playerRecord.LastSeenUtc = ""; } playerRecord = value; if (playerRecord.FirstSeenUtc == null) { playerRecord.FirstSeenUtc = ""; } } } public static bool Save(Dictionary records) { try { Directory.CreateDirectory(FolderPath); string contents = JsonConvert.SerializeObject((object)records, (Formatting)1); File.WriteAllText(TempPath, contents); if (File.Exists(FilePath)) { File.Replace(TempPath, FilePath, BackupPath); } else { File.Move(TempPath, FilePath); } return true; } catch (Exception ex) { MelonLogger.Error("[LabRP] Failed to save economy data: " + ex.Message); return false; } } } public static class ServerEconomy { private struct RequestWindow { public float Start; public int Count; public bool Warned; } private static Dictionary _records = new Dictionary(); private static bool _hosting = false; private static bool _dirty = false; private static float _salaryTimer = 0f; private static float _autosaveTimer = 0f; private const float AutosaveIntervalSeconds = 60f; private const int MaxRequestsPerWindow = 8; private const float RequestWindowSeconds = 5f; private static readonly Dictionary _requestWindows = new Dictionary(); public static bool IsHosting => _hosting; public static void OnServerStarted() { if (_dirty && _records.Count > 0 && EconomySave.Save(_records)) { _dirty = false; } _records = EconomySave.Load(); _hosting = true; _dirty = false; _salaryTimer = 0f; _autosaveTimer = 0f; _requestWindows.Clear(); PlayerID localID = PlayerIDManager.LocalID; if (localID != null) { EnsurePlayer(localID); } ClientEconomy.ApplySnapshot(BuildSnapshot()); MelonLogger.Msg("[LabRP] Economy started. Balances will be saved on this machine."); } public static void OnPlayerJoined(PlayerID playerID) { if (_hosting && playerID != null) { EnsurePlayer(playerID); BroadcastSnapshot(); } } public static void OnPlayerLeft(PlayerID playerID) { if (_hosting && playerID != null) { TouchRecord(playerID); _requestWindows.Remove(playerID.SmallID); Save(); BroadcastSnapshot(); } } public static void OnUsernameChanged(PlayerID playerID) { if (_hosting && playerID != null) { TouchRecord(playerID); } } public static void OnDisconnected() { if (_hosting) { SaveWithRetries(); MelonLogger.Msg("[LabRP] Economy saved and stopped."); } _hosting = false; _salaryTimer = 0f; _requestWindows.Clear(); } public static void OnUpdate() { if (!_hosting || !NetworkInfo.HasServer) { return; } float unscaledDeltaTime = Time.unscaledDeltaTime; float num = LabRPConfig.SalaryIntervalMinutes.Value; if (float.IsNaN(num) || float.IsInfinity(num)) { num = 5f; } float num2 = Mathf.Max(0.25f, num) * 60f; _salaryTimer = Mathf.Min(_salaryTimer + unscaledDeltaTime, num2); if (_salaryTimer >= num2) { _salaryTimer = 0f; PayEveryoneSalary(); } _autosaveTimer += unscaledDeltaTime; if (_autosaveTimer >= 60f) { _autosaveTimer = 0f; if (_dirty) { StampOnlinePlayers(); Save(); } } } public static void SaveIfHosting() { if (_hosting) { StampOnlinePlayers(); SaveWithRetries(); } } public static void PayEveryoneSalary() { if (!_hosting) { return; } long num = Math.Clamp(LabRPConfig.SalaryAmount.Value, 0L, 999999999999L); if (num <= 0) { return; } foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid) { ChangeBalance(playerID.PlatformID, num, BalanceReason.Paycheck, "Paycheck"); } } } public static void TryTransfer(byte senderSmallID, ulong targetPlatformID, long amount) { if (!_hosting) { return; } PlayerID playerID = PlayerIDManager.GetPlayerID(senderSmallID); if (playerID == null || !CheckRequestBudget(senderSmallID)) { return; } PlayerID playerID2 = PlayerIDManager.GetPlayerID(targetPlatformID); if (amount <= 0) { Deny(playerID, "Invalid amount."); return; } if (playerID2 == null || !playerID2.IsValid) { Deny(playerID, "That player is no longer in the lobby."); return; } if (playerID2.PlatformID == playerID.PlatformID) { Deny(playerID, "You can't pay yourself."); return; } PlayerRecord playerRecord = EnsurePlayer(playerID); PlayerRecord playerRecord2 = EnsurePlayer(playerID2); if (playerRecord.Balance < amount) { Deny(playerID, "Not enough money."); return; } long num = 999999999999L - playerRecord2.Balance; if (amount > num) { Deny(playerID, "They can't hold that much money."); return; } string text = ResolveName(playerID); string text2 = ResolveName(playerID2); ChangeBalance(playerID.PlatformID, -amount, BalanceReason.PaymentSent, text2); ChangeBalance(playerID2.PlatformID, amount, BalanceReason.PaymentReceived, text); MelonLogger.Msg($"[LabRP] {text} paid {text2} ${amount:N0}."); } public static void AdminGive(ulong platformID, long amount) { if (_hosting && amount > 0 && IsOnline(platformID)) { ChangeBalance(platformID, amount, BalanceReason.Admin, "Host grant"); } } public static void AdminTake(ulong platformID, long amount) { if (_hosting && amount > 0 && IsOnline(platformID)) { ChangeBalance(platformID, -amount, BalanceReason.Admin, "Host deduction"); } } public static void AdminSet(ulong platformID, long value) { if (_hosting && IsOnline(platformID)) { PlayerRecord orCreateRecord = GetOrCreateRecord(platformID); long num = LabRPConfig.ClampBalance(value); long delta = num - orCreateRecord.Balance; orCreateRecord.Balance = num; MarkDirty(); BroadcastUpdate(platformID, orCreateRecord.Balance, delta, BalanceReason.Admin, "Host set"); } } public static void ResetAllBalances() { if (!_hosting) { return; } _records.Clear(); foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid) { EnsurePlayer(playerID); } } MarkDirty(); Save(); BroadcastSnapshot(); ClientEconomy.ApplySnapshot(BuildSnapshot()); MelonLogger.Msg("[LabRP] All balances were reset by the host."); } public static long GetBalance(ulong platformID) { if (!_records.TryGetValue(platformID, out var value)) { return 0L; } return value.Balance; } public static bool IsOnline(ulong platformID) { PlayerID playerID = PlayerIDManager.GetPlayerID(platformID); if (playerID != null) { return playerID.IsValid; } return false; } private static bool CheckRequestBudget(byte senderSmallID) { float unscaledTime = Time.unscaledTime; if (!_requestWindows.TryGetValue(senderSmallID, out var value) || unscaledTime - value.Start > 5f) { value = new RequestWindow { Start = unscaledTime, Count = 0, Warned = false }; } value.Count++; bool num = value.Count <= 8; if (!num && !value.Warned) { value.Warned = true; MelonLogger.Warning($"[LabRP] Dropping flood of pay requests from player {senderSmallID}."); } _requestWindows[senderSmallID] = value; return num; } private static void ChangeBalance(ulong platformID, long amount, BalanceReason reason, string detail) { PlayerRecord orCreateRecord = GetOrCreateRecord(platformID); long balance = orCreateRecord.Balance; orCreateRecord.Balance = LabRPConfig.ClampBalance(SaturatingAdd(balance, amount)); long delta = orCreateRecord.Balance - balance; MarkDirty(); BroadcastUpdate(platformID, orCreateRecord.Balance, delta, reason, detail); } private static long SaturatingAdd(long a, long b) { long num = a + b; if (b > 0 && num < a) { return long.MaxValue; } if (b < 0 && num > a) { return long.MinValue; } return num; } private static void Deny(PlayerID payer, string message) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(new BalanceUpdateData { PlatformID = payer.PlatformID, NewBalance = GetBalance(payer.PlatformID), Delta = 0L, Reason = BalanceReason.Denied, Detail = message }, new MessageRoute(payer.SmallID, (NetworkChannel)0)); } private static void BroadcastUpdate(ulong platformID, long newBalance, long delta, BalanceReason reason, string detail) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(new BalanceUpdateData { PlatformID = platformID, NewBalance = newBalance, Delta = delta, Reason = reason, Detail = (detail ?? "") }, new MessageRoute((RelayType)2, (NetworkChannel)0)); } private static void BroadcastSnapshot() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(BuildSnapshot(), new MessageRoute((RelayType)2, (NetworkChannel)0)); } private static EconomySnapshotData BuildSnapshot() { EconomySnapshotData economySnapshotData = new EconomySnapshotData(); foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid) { PlayerRecord orCreateRecord = GetOrCreateRecord(playerID.PlatformID); economySnapshotData.Entries.Add(new SnapshotEntry { PlatformID = playerID.PlatformID, Name = ResolveName(playerID), Balance = orCreateRecord.Balance }); } } return economySnapshotData; } private static PlayerRecord EnsurePlayer(PlayerID playerID) { PlayerRecord orCreateRecord = GetOrCreateRecord(playerID.PlatformID); TouchRecord(playerID); return orCreateRecord; } private static PlayerRecord GetOrCreateRecord(ulong platformID) { if (!_records.TryGetValue(platformID, out var value)) { value = new PlayerRecord { Name = "Unknown", Balance = LabRPConfig.ClampBalance(LabRPConfig.StartingBalance.Value), FirstSeenUtc = DateTime.UtcNow.ToString("o"), LastSeenUtc = DateTime.UtcNow.ToString("o") }; _records[platformID] = value; MarkDirty(); } return value; } private static void TouchRecord(PlayerID playerID) { if (playerID != null && _records.TryGetValue(playerID.PlatformID, out var value)) { PlayerMetadata metadata = playerID.Metadata; object name; if (metadata == null) { name = null; } else { MetadataVariable username = metadata.Username; name = ((username != null) ? username.GetValue() : null); } string text = NameUtil.Clean((string)name); if (!string.IsNullOrWhiteSpace(text) && text != value.Name) { value.Name = text; MarkDirty(); } value.LastSeenUtc = DateTime.UtcNow.ToString("o"); } } private static void StampOnlinePlayers() { foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid) { TouchRecord(playerID); } } } public static string ResolveName(PlayerID playerID) { if (playerID == null) { return "Unknown"; } PlayerMetadata metadata = playerID.Metadata; object name; if (metadata == null) { name = null; } else { MetadataVariable username = metadata.Username; name = ((username != null) ? username.GetValue() : null); } string text = NameUtil.Clean((string)name); if (!string.IsNullOrWhiteSpace(text)) { return text; } if (!_records.TryGetValue(playerID.PlatformID, out var value) || string.IsNullOrWhiteSpace(value.Name) || !(value.Name != "Unknown")) { return $"Player {playerID.SmallID}"; } return value.Name; } private static void MarkDirty() { _dirty = true; } private static void Save() { if (EconomySave.Save(_records)) { _dirty = false; } } private static void SaveWithRetries() { for (int i = 0; i < 3; i++) { if (EconomySave.Save(_records)) { _dirty = false; return; } Thread.Sleep(150); } MelonLogger.Error("[LabRP] Could not save the economy after multiple attempts. Changes since the last successful save may be lost."); } } }