Decompiled source of LabRP v1.1.2

Mods/LabRP.dll

Decompiled a day ago
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<LabRPModule>();
		}

		[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<long> StartingBalance { get; private set; }

		public static MelonPreferences_Entry<long> SalaryAmount { get; private set; }

		public static MelonPreferences_Entry<float> SalaryIntervalMinutes { get; private set; }

		public static MelonPreferences_Entry<bool> HudEnabled { get; private set; }

		public static MelonPreferences_Entry<bool> HudBillboard { get; private set; }

		public static MelonPreferences_Entry<float> HudScale { get; private set; }

		public static MelonPreferences_Entry<float> HudOffsetX { get; private set; }

		public static MelonPreferences_Entry<float> HudOffsetY { get; private set; }

		public static MelonPreferences_Entry<float> HudOffsetZ { get; private set; }

		public static MelonPreferences_Entry<float> HudYaw { get; private set; }

		public static void Initialize()
		{
			Category = MelonPreferences.CreateCategory("LabRP");
			StartingBalance = Category.CreateEntry<long>("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<long>("SalaryAmount", 200L, "Salary Amount", "Money paid to every connected player each paycheck.", false, false, (ValueValidator)null, (string)null);
			SalaryIntervalMinutes = Category.CreateEntry<float>("SalaryIntervalMinutes", 5f, "Salary Interval (Minutes)", "How often paychecks are handed out.", false, false, (ValueValidator)null, (string)null);
			HudEnabled = Category.CreateEntry<bool>("HudEnabled", true, "Wrist HUD Enabled", (string)null, false, false, (ValueValidator)null, (string)null);
			HudBillboard = Category.CreateEntry<bool>("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<float>("HudScale", 1f, "Wrist HUD Scale", (string)null, false, false, (ValueValidator)null, (string)null);
			HudOffsetX = Category.CreateEntry<float>("HudOffsetX", 0f, "Wrist HUD Offset X", (string)null, false, false, (ValueValidator)null, (string)null);
			HudOffsetY = Category.CreateEntry<float>("HudOffsetY", 0.055f, "Wrist HUD Offset Y", (string)null, false, false, (ValueValidator)null, (string)null);
			HudOffsetZ = Category.CreateEntry<float>("HudOffsetZ", -0.085f, "Wrist HUD Offset Z", (string)null, false, false, (ValueValidator)null, (string)null);
			HudYaw = Category.CreateEntry<float>("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<float> 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<int>)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<PayRequestMessage, PayRequestData>(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<KeyValuePair<ulong, ClientEconomy.CachedPlayer>> 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<ulong, ClientEconomy.CachedPlayer> 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<int>)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<float>)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<int>)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<bool>)delegate(bool value)
			{
				LabRPConfig.HudEnabled.Value = value;
				LabRPConfig.Save();
			});
			_hudPage.CreateBool("Always Face You", Color.white, LabRPConfig.HudBillboard.Value, (Action<bool>)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<float>)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<float>)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<float> hudOffsetX = LabRPConfig.HudOffsetX;
				hudOffsetX.Value += dx;
				MelonPreferences_Entry<float> hudOffsetY = LabRPConfig.HudOffsetY;
				hudOffsetY.Value += dy;
				MelonPreferences_Entry<float> hudOffsetZ = LabRPConfig.HudOffsetZ;
				hudOffsetZ.Value += dz;
				WristHud.ApplyTransformPrefs();
			});
		}

		private static IEnumerable<PlayerID> 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<TextMeshPro>();
			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<Object> obj = Resources.FindObjectsOfTypeAll(Il2CppType.Of<TMP_FontAsset>());
				TMP_FontAsset val = null;
				foreach (Object item in (Il2CppArrayBase<Object>)(object)obj)
				{
					TMP_FontAsset val2 = ((Il2CppObjectBase)item).TryCast<TMP_FontAsset>();
					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<PayRequestData>();
				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<BalanceReason>(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<BalanceUpdateData>());
			}
		}
	}
	public class SnapshotEntry
	{
		public ulong PlatformID;

		public string Name = "";

		public long Balance;
	}
	public class EconomySnapshotData : INetSerializable
	{
		public List<SnapshotEntry> Entries = new List<SnapshotEntry>();

		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<SnapshotEntry>();
					return;
				}
				Entries = new List<SnapshotEntry>(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<EconomySnapshotData>());
			}
		}
	}
	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<PlayerID, string, string> <5>__OnMetadataChanged;

			public static Action<long, long, BalanceReason, string> <6>__OnLocalBalanceChanged;
		}

		private Action<RigManager> _onRigCreated;

		private Action<long, long, BalanceReason, string> _onLocalChanged;

		private Action<PlayerID, string, string> _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<RigManager>)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<RigManager>)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<ulong, CachedPlayer> _cache = new Dictionary<ulong, CachedPlayer>();

		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<long, long, BalanceReason, string> 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<KeyValuePair<ulong, CachedPlayer>> GetLeaderboard()
		{
			return _cache.OrderByDescending((KeyValuePair<ulong, CachedPlayer> 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<ulong, PlayerRecord> Load()
		{
			Dictionary<ulong, PlayerRecord> 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<ulong, PlayerRecord>();
			}
			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<ulong, PlayerRecord> TryLoadFile(string path, string label)
		{
			try
			{
				if (!File.Exists(path))
				{
					return null;
				}
				return JsonConvert.DeserializeObject<Dictionary<ulong, PlayerRecord>>(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<ulong, PlayerRecord> 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<ulong, PlayerRecord> 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<ulong, PlayerRecord> _records = new Dictionary<ulong, PlayerRecord>();

		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<byte, RequestWindow> _requestWindows = new Dictionary<byte, RequestWindow>();

		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<BalanceUpdateMessage, BalanceUpdateData>(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<BalanceUpdateMessage, BalanceUpdateData>(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<EconomySnapshotMessage, EconomySnapshotData>(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.");
		}
	}
}