Decompiled source of SharedLGUCurrency v1.0.0

SharedLGUCurrency.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SharedLGUCurrency")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SharedLGUCurrency")]
[assembly: AssemblyTitle("SharedLGUCurrency")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 SharedLGUCurrency
{
	[HarmonyPatch]
	internal static class AddCurrencyAmountPatch
	{
		private static MethodBase? TargetMethod()
		{
			return AccessTools.Method(LguReflection.CurrencyManagerType, "AddCurrencyAmount", new Type[2]
			{
				typeof(int),
				typeof(bool)
			}, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPrefix]
		private static bool Prefix(int amount, bool trackSpent)
		{
			if (MilkMolarsPatches.InMolarActivation)
			{
				return false;
			}
			if (!NetworkSyncManager.IsReady)
			{
				return true;
			}
			NetworkSyncManager.RequestDelta(amount, trackSpent, DetectSource());
			return false;
		}

		private static string DetectSource()
		{
			try
			{
				StackTrace stackTrace = new StackTrace(2, fNeedFileInfo: false);
				for (int i = 0; i < Math.Min(stackTrace.FrameCount, 8); i++)
				{
					Type type = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType;
					if (type != null && type.FullName != null && !type.FullName.StartsWith("MoreShipUpgrades.Managers.CurrencyManager", StringComparison.Ordinal))
					{
						return type.FullName;
					}
				}
			}
			catch
			{
			}
			return "LGU AddCurrencyAmount";
		}
	}
	[HarmonyPatch]
	internal static class RemoveCurrencyAmountPatch
	{
		private static MethodBase? TargetMethod()
		{
			return AccessTools.Method(LguReflection.CurrencyManagerType, "RemoveCurrencyAmount", new Type[2]
			{
				typeof(int),
				typeof(bool)
			}, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPrefix]
		private static bool Prefix(int amount, bool trackSpent)
		{
			if (!NetworkSyncManager.IsReady)
			{
				return true;
			}
			NetworkSyncManager.RequestDelta(-amount, trackSpent, "LGU RemoveCurrencyAmount");
			return false;
		}
	}
	[HarmonyPatch]
	internal static class AtomicAlternateCurrencyPurchasePatch
	{
		private static MethodBase? TargetMethod()
		{
			Type type = AccessTools.TypeByName("MoreShipUpgrades.UI.Application.UpgradeStoreApplication");
			if (type == null)
			{
				return null;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "PurchaseUpgradeAlternateCurrency" && methodInfo.GetParameters().Length == 3)
				{
					return methodInfo;
				}
			}
			return null;
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPrefix]
		private static bool Prefix(object __0, int __1, Action __2)
		{
			string nodeName = LguReflection.GetNodeName(__0);
			if (string.IsNullOrEmpty(nodeName))
			{
				LguReflection.ShowResult("Shared purchase rejected", "Could not identify the LGU upgrade.");
				return false;
			}
			NetworkSyncManager.RequestPurchase(nodeName, __1);
			try
			{
				__2?.Invoke();
			}
			catch (Exception ex)
			{
				Plugin.Verbose("Could not return from LGU purchase screen: " + ex.Message);
			}
			return false;
		}
	}
	[HarmonyPatch]
	internal static class QuotaCurrencyPatch
	{
		private static MethodBase? TargetMethod()
		{
			return AccessTools.Method(LguReflection.CurrencyManagerType, "AddCurrencyAmountFromQuotaClientRpc", new Type[1] { typeof(int) }, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPrefix]
		private static bool Prefix(object __instance, int quotaFullfilled)
		{
			if (Plugin.Settings.DisableQuotaCurrency.Value)
			{
				Plugin.Verbose("Suppressed LGU quota currency payout.");
				return false;
			}
			if (NetworkSyncManager.IsServer && __instance.GetType().GetMethod("GetCurrencyAmountFromQuota", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(__instance, new object[1] { quotaFullfilled }) is int delta)
			{
				SharedCurrencyManager.Instance.HostApplyDelta(delta, trackSpent: false, NetworkManager.Singleton.LocalClientId, "LGU quota", out string _);
			}
			return false;
		}
	}
	[HarmonyPatch]
	internal static class CurrencyResetPatch
	{
		private static MethodBase? TargetMethod()
		{
			return AccessTools.Method(LguReflection.CurrencyManagerType, "ResetAllValues", (Type[])null, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			if (Plugin.Settings.ResetSharedCurrencyOnLGUReset.Value && NetworkSyncManager.IsServer)
			{
				SharedCurrencyManager.Instance.HostReset("LGU reset");
			}
		}
	}
	[HarmonyPatch]
	internal static class LguSaveLoadPatch
	{
		private static MethodBase? TargetMethod()
		{
			return AccessTools.Method(LguReflection.LguStoreType, "UpdateUpgradeBus", new Type[2]
			{
				typeof(bool),
				typeof(bool)
			}, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			if (NetworkSyncManager.IsReady)
			{
				if (NetworkSyncManager.IsServer)
				{
					SharedCurrencyManager.Instance.HostImportFromLgu("save/load");
				}
				else
				{
					NetworkSyncManager.RequestSync();
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class UnsafePlayerCreditOperationPatch
	{
		[HarmonyTargetMethods]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			(string, string)[] array = new(string, string)[3]
			{
				("MoreShipUpgrades.UI.Application.TradePlayerCreditsApplication", "ExecuteTrade"),
				("MoreShipUpgrades.UI.Application.ConvertPlayerCreditApplication", "TryConvertPCsToCredits"),
				("MoreShipUpgrades.UI.Application.ConvertPlayerCreditApplication", "TryConvertCreditsToPCs")
			};
			(string TypeName, string MethodName)[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				(string, string) tuple = array2[i];
				string item = tuple.Item1;
				string methodName = tuple.Item2;
				Type type = AccessTools.TypeByName(item);
				if (type == null)
				{
					continue;
				}
				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.Name == methodName)
					{
						yield return methodInfo;
					}
				}
			}
		}

		[HarmonyPrefix]
		private static bool Prefix(object[] __args)
		{
			if (!Plugin.Settings.DisableUnsafePlayerCreditOperations.Value)
			{
				return true;
			}
			LguReflection.ShowResult("SharedLGUCurrency", "This per-player Player Credit operation is disabled for the shared crew wallet.");
			foreach (object obj in __args)
			{
				if (obj is Action action)
				{
					try
					{
						action();
					}
					catch
					{
					}
					break;
				}
			}
			return false;
		}
	}
	[HarmonyPatch]
	internal static class UnsafePlayerCreditRefundPatch
	{
		private static MethodBase? TargetMethod()
		{
			return AccessTools.TypeByName("MoreShipUpgrades.UI.Application.UpgradeStoreApplication")?.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "RefundUpgrade" && method.GetParameters().Length == 3);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		[HarmonyPrefix]
		private static bool Prefix(object __1, Action __2)
		{
			if (!Plugin.Settings.DisableUnsafePlayerCreditOperations.Value || !string.Equals(__1.ToString(), "AlternateCurrency", StringComparison.Ordinal))
			{
				return true;
			}
			LguReflection.ShowResult("SharedLGUCurrency", "Player Credit refunds are disabled because LGU 3.14.1 does not host-authorize them.");
			try
			{
				__2?.Invoke();
			}
			catch
			{
			}
			return false;
		}
	}
	internal static class LguReflection
	{
		internal const string CurrencyManagerTypeName = "MoreShipUpgrades.Managers.CurrencyManager";

		internal const string LguStoreTypeName = "MoreShipUpgrades.Managers.LguStore";

		internal const string UpgradeBusTypeName = "MoreShipUpgrades.Managers.UpgradeBus";

		internal const string UpgradeStoreApplicationTypeName = "MoreShipUpgrades.UI.Application.UpgradeStoreApplication";

		internal static Type? CurrencyManagerType => AccessTools.TypeByName("MoreShipUpgrades.Managers.CurrencyManager");

		internal static Type? LguStoreType => AccessTools.TypeByName("MoreShipUpgrades.Managers.LguStore");

		internal static Type? UpgradeBusType => AccessTools.TypeByName("MoreShipUpgrades.Managers.UpgradeBus");

		internal static object? CurrencyInstance => GetStaticMember(CurrencyManagerType, "Instance");

		internal static object? LguStoreInstance => GetStaticMember(LguStoreType, "Instance");

		internal static object? UpgradeBusInstance => GetStaticMember(UpgradeBusType, "Instance");

		internal static int ReadBalance()
		{
			return GetInt(CurrencyInstance, "CurrencyAmount");
		}

		internal static int ReadSpent()
		{
			return GetInt(CurrencyInstance, "SpentCurrencyAmount");
		}

		internal static void MirrorBalance(int balance, int spent)
		{
			object currencyInstance = CurrencyInstance;
			if (currencyInstance != null)
			{
				SetMember(currencyInstance, "CurrencyAmount", balance);
				SetMember(currencyInstance, "SpentCurrencyAmount", spent);
			}
		}

		internal static int GetMaximumBalance()
		{
			Type currencyManagerType = CurrencyManagerType;
			if (currencyManagerType == null)
			{
				return int.MaxValue;
			}
			int num = ((GetStaticMember(currencyManagerType, "MaximumAmountPerPlayer") is int num2) ? num2 : (-1));
			if (num <= 0)
			{
				return int.MaxValue;
			}
			return num;
		}

		internal static string GetNodeName(object node)
		{
			return (GetMember(node, "OriginalName") as string) ?? string.Empty;
		}

		internal static string GetDisplayName(object node)
		{
			return (GetMember(node, "Name") as string) ?? GetNodeName(node);
		}

		internal static bool TryResolvePurchase(string upgradeName, int expectedCurrencyPrice, out object? node, out int actualCurrencyPrice, out bool increment, out string rejection)
		{
			node = null;
			actualCurrencyPrice = 0;
			increment = false;
			rejection = string.Empty;
			object upgradeBusInstance = UpgradeBusInstance;
			object currencyInstance = CurrencyInstance;
			if (upgradeBusInstance == null || currencyInstance == null)
			{
				rejection = "LGU is not ready yet.";
				return false;
			}
			node = upgradeBusInstance.GetType().GetMethod("GetUpgradeNode", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(upgradeBusInstance, new object[1] { upgradeName });
			if (node == null)
			{
				rejection = "Unknown LGU upgrade.";
				return false;
			}
			if (!GetBool(node, "SharedUpgrade"))
			{
				rejection = "LGU did not mark this upgrade as shared. Enable 'Convert all upgrades to be shared.'.";
				return false;
			}
			bool flag = GetBool(node, "Unlocked");
			int num = GetInt(node, "CurrentUpgrade");
			int num2 = GetInt(node, "MaxUpgrade");
			if (flag && num >= num2)
			{
				rejection = "Upgrade is already at its maximum level.";
				return false;
			}
			if (!(node.GetType().GetMethod("GetCurrentPrice", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(node, null) is int num3) || num3 < 0 || num3 == int.MaxValue)
			{
				rejection = "LGU returned an invalid upgrade price.";
				return false;
			}
			if (!(currencyInstance.GetType().GetMethod("GetCurrencyAmountFromCredits", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(currencyInstance, new object[1] { num3 }) is int num4) || num4 < 0)
			{
				rejection = "LGU could not convert the upgrade price to Player Credits.";
				return false;
			}
			actualCurrencyPrice = num4;
			if (expectedCurrencyPrice != actualCurrencyPrice)
			{
				rejection = "The displayed price is stale; reopen the upgrade screen and try again.";
				return false;
			}
			increment = flag;
			return true;
		}

		internal static void ApplySharedPurchase(object node, bool increment)
		{
			object obj = LguStoreInstance ?? throw new InvalidOperationException("LGU store is not available.");
			MethodInfo methodInfo = obj.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "HandleUpgrade" && method.GetParameters().Length == 3);
			if (methodInfo == null)
			{
				throw new MissingMethodException(obj.GetType().FullName, "HandleUpgrade(CustomTerminalNode, bool, bool)");
			}
			methodInfo.Invoke(obj, new object[3] { node, increment, false });
		}

		internal static void ShowResult(string title, string message)
		{
			try
			{
				Type type = AccessTools.TypeByName("HUDManager");
				object staticMember = GetStaticMember(type, "Instance");
				if (staticMember == null)
				{
					return;
				}
				MethodInfo methodInfo = (from method in staticMember.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where method.Name == "DisplayTip"
					orderby method.GetParameters().Length
					select method).FirstOrDefault((MethodInfo method) => method.GetParameters().Length >= 2);
				if (!(methodInfo == null))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					object[] array = new object[parameters.Length];
					array[0] = title;
					array[1] = message;
					for (int num = 2; num < array.Length; num++)
					{
						array[num] = (parameters[num].HasDefaultValue ? parameters[num].DefaultValue : GetDefault(parameters[num].ParameterType));
					}
					methodInfo.Invoke(staticMember, array);
				}
			}
			catch (Exception ex)
			{
				Plugin.Verbose("Could not show HUD result: " + ex.Message);
			}
		}

		internal static object? GetMember(object? instance, string name)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property.GetValue(instance, null);
			}
			return type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance);
		}

		internal static void SetMember(object instance, string name, object value)
		{
			Type type = instance.GetType();
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if ((object)property != null && property.CanWrite)
			{
				property.SetValue(instance, value, null);
			}
			else
			{
				type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(instance, value);
			}
		}

		internal static object? GetStaticMember(Type? type, string name)
		{
			if (type == null)
			{
				return null;
			}
			PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property.GetValue(null, null);
			}
			return type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
		}

		internal static int GetInt(object? instance, string name)
		{
			object member = GetMember(instance, name);
			if (member is int)
			{
				return (int)member;
			}
			return 0;
		}

		internal static bool GetBool(object? instance, string name)
		{
			object member = GetMember(instance, name);
			bool flag = default(bool);
			int num;
			if (member is bool)
			{
				flag = (bool)member;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static object? GetDefault(Type type)
		{
			if (!type.IsValueType)
			{
				return null;
			}
			return Activator.CreateInstance(type);
		}
	}
	[HarmonyPatch]
	internal static class MilkMolarsPatches
	{
		private static readonly HashSet<ulong> CreditedMolarIds = new HashSet<ulong>();

		private static bool _forcedConfigLogged;

		[ThreadStatic]
		private static int _activationDepth;

		internal static bool InMolarActivation => _activationDepth > 0;

		[HarmonyTargetMethods]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			string[] array = new string[2] { "MilkMolars.MilkMolarBehavior", "MilkMolars.MegaMilkMolarBehavior" };
			foreach (string text in array)
			{
				Type type = AccessTools.TypeByName(text);
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "ActivateMolarRpc", new Type[1] { typeof(int) }, (Type[])null));
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		[HarmonyPrefix]
		private static void Prefix(object __instance, int __0)
		{
			_activationDepth++;
			if (!NetworkSyncManager.IsServer)
			{
				return;
			}
			NetworkBehaviour val = (NetworkBehaviour)((__instance is NetworkBehaviour) ? __instance : null);
			if (val == null)
			{
				return;
			}
			ulong networkObjectId = val.NetworkObjectId;
			if (CreditedMolarIds.Contains(networkObjectId))
			{
				Plugin.Verbose($"Ignored duplicate molar activation for network object {networkObjectId}.");
				return;
			}
			if (__0 < 0)
			{
				Plugin.Verbose($"Ignored molar {networkObjectId}: no activating player was supplied.");
				return;
			}
			if (!ValidateReturnToShip(__instance, (ulong)__0))
			{
				Plugin.Log.LogWarning((object)$"Rejected early ReturnToShip activation for molar {networkObjectId} from player {__0}.");
				return;
			}
			int num = ReadNetworkVariableInt(LguReflection.GetMember(__instance, "ContributeAmount"));
			if (num > 0)
			{
				CreditedMolarIds.Add(networkObjectId);
				string molarName = (__instance.GetType().Name.StartsWith("Mega", StringComparison.Ordinal) ? "Mega Milk Molar" : "Milk Molar");
				SharedCurrencyManager.Instance.HostCreditMolar(num, (ulong)__0, molarName);
			}
		}

		[HarmonyFinalizer]
		private static Exception? Finalizer(Exception? __exception)
		{
			if (_activationDepth > 0)
			{
				_activationDepth--;
			}
			return __exception;
		}

		internal static void ResetSession()
		{
			CreditedMolarIds.Clear();
		}

		internal static void ApplyForcedConfig()
		{
			if (Plugin.Settings.ForceSharedContributeAmountToZero.Value)
			{
				string[] array = new string[2] { "MilkMolars.MilkMolarBehavior", "MilkMolars.MegaMilkMolarBehavior" };
				foreach (string text in array)
				{
					AccessTools.TypeByName(text)?.GetField("cfgSharedContributeAmount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(null, 0);
				}
				if (!_forcedConfigLogged)
				{
					_forcedConfigLogged = true;
					Plugin.Log.LogInfo((object)"Forced MilkMolars Shared Contribute Amount to 0.");
				}
			}
		}

		private static bool ValidateReturnToShip(object instance, ulong playerId)
		{
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			object member = LguReflection.GetMember(instance, "ActivateMethod");
			if (!string.Equals(LguReflection.GetMember(member, "Value")?.ToString(), "ReturnToShip", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			try
			{
				Type type = AccessTools.TypeByName("StartOfRound");
				object staticMember = LguReflection.GetStaticMember(type, "Instance");
				if (staticMember == null)
				{
					return false;
				}
				object member2 = LguReflection.GetMember(staticMember, "allPlayerScripts");
				if (member2 is Array array)
				{
					foreach (object item in array)
					{
						if (item != null && LguReflection.GetMember(item, "actualClientId") is ulong num && num == playerId && LguReflection.GetBool(item, "isInHangarShipRoom"))
						{
							return true;
						}
					}
				}
				object member3 = LguReflection.GetMember(staticMember, "shipInnerRoomBounds");
				Collider val = (Collider)((member3 is Collider) ? member3 : null);
				if (val != null)
				{
					Component val2 = (Component)((instance is Component) ? instance : null);
					if (val2 != null)
					{
						Bounds bounds = val.bounds;
						((Bounds)(ref bounds)).Expand(1f);
						return ((Bounds)(ref bounds)).Contains(val2.transform.position);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Verbose("ReturnToShip validation failed: " + ex.Message);
			}
			return false;
		}

		private static int ReadNetworkVariableInt(object? networkVariable)
		{
			object member = LguReflection.GetMember(networkVariable, "Value");
			if (member is int)
			{
				return (int)member;
			}
			return 0;
		}
	}
	[HarmonyPatch]
	internal static class MilkMolarStartConfigPatch
	{
		[HarmonyTargetMethods]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			string[] array = new string[2] { "MilkMolars.MilkMolarBehavior", "MilkMolars.MegaMilkMolarBehavior" };
			foreach (string text in array)
			{
				Type type = AccessTools.TypeByName(text);
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Start", (Type[])null, (Type[])null));
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		[HarmonyPrefix]
		private static void Prefix()
		{
			MilkMolarsPatches.ApplyForcedConfig();
		}
	}
	internal sealed class ModConfig
	{
		internal ConfigEntry<bool> Enabled { get; }

		internal ConfigEntry<bool> ForceSharedContributeAmountToZero { get; }

		internal ConfigEntry<bool> DisableQuotaCurrency { get; }

		internal ConfigEntry<bool> ResetSharedCurrencyOnLGUReset { get; }

		internal ConfigEntry<bool> DisableUnsafePlayerCreditOperations { get; }

		internal ConfigEntry<bool> VerboseLogging { get; }

		internal ConfigEntry<int> MaximumExternalDeltaPerRequest { get; }

		internal ModConfig(ConfigFile config)
		{
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable the shared, host-authoritative LGU currency wallet.");
			ForceSharedContributeAmountToZero = config.Bind<bool>("MilkMolars", "ForceSharedContributeAmountToZero", true, "Force MilkMolars' Shared Contribute Amount to zero so one molar can never reward every player.");
			DisableQuotaCurrency = config.Bind<bool>("Currency", "DisableQuotaCurrency", true, "Suppress LGU's automatic quota-to-player-credit payout.");
			ResetSharedCurrencyOnLGUReset = config.Bind<bool>("Currency", "ResetSharedCurrencyOnLGUReset", true, "Reset the crew wallet whenever LGU resets its CurrencyManager.");
			DisableUnsafePlayerCreditOperations = config.Bind<bool>("Currency", "DisableUnsafePlayerCreditOperations", true, "Disable LGU's per-player trade/conversion and Player Credit refund UIs; LGU 3.14.1 performs their side effects before host confirmation.");
			VerboseLogging = config.Bind<bool>("Networking", "VerboseLogging", false, "Log network requests, mirrors and synchronization details.");
			MaximumExternalDeltaPerRequest = config.Bind<int>("Networking", "MaximumExternalDeltaPerRequest", 1000, "Reject non-purchase currency API requests whose absolute delta exceeds this value. Set to 0 to disable the limit.");
		}
	}
	internal sealed class NetworkSyncManager : MonoBehaviour
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__HandleBalance;

			public static HandleNamedMessageDelegate <1>__HandlePurchaseResult;

			public static HandleNamedMessageDelegate <2>__HandleDeltaRequest;

			public static HandleNamedMessageDelegate <3>__HandlePurchaseRequest;

			public static HandleNamedMessageDelegate <4>__HandleSyncRequest;

			public static Action<ulong> <5>__HandleClientConnected;
		}

		private const string Protocol = "timo.sharedlgucurrency/v1/";

		private const string DeltaRequestMessage = "timo.sharedlgucurrency/v1/delta";

		private const string PurchaseRequestMessage = "timo.sharedlgucurrency/v1/purchase";

		private const string SyncRequestMessage = "timo.sharedlgucurrency/v1/sync-request";

		private const string BalanceMessage = "timo.sharedlgucurrency/v1/balance";

		private const string PurchaseResultMessage = "timo.sharedlgucurrency/v1/purchase-result";

		private static NetworkSyncManager? _instance;

		private static NetworkManager? _registeredNetwork;

		private static readonly Dictionary<ulong, HashSet<ulong>> SeenNoncesByClient = new Dictionary<ulong, HashSet<ulong>>();

		private static ulong _nextNonce;

		internal static bool IsReady
		{
			get
			{
				if ((Object)(object)_registeredNetwork != (Object)null)
				{
					return _registeredNetwork.IsListening;
				}
				return false;
			}
		}

		internal static bool IsServer
		{
			get
			{
				if (IsReady)
				{
					return _registeredNetwork.IsServer;
				}
				return false;
			}
		}

		private void Awake()
		{
			if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
			else
			{
				_instance = this;
			}
		}

		private void Update()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)(object)_registeredNetwork) || !((Object)(object)singleton != (Object)null) || !singleton.IsListening)
			{
				Unregister();
				if (!((Object)(object)singleton == (Object)null) && singleton.IsListening)
				{
					Register(singleton);
				}
			}
		}

		private void OnDestroy()
		{
			Unregister();
		}

		private static void Register(NetworkManager network)
		{
			//IL_0034: 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_003f: 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_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			//IL_00ae: 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_00b9: Expected O, but got Unknown
			//IL_00d4: 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)
			//IL_00df: Expected O, but got Unknown
			_registeredNetwork = network;
			SeenNoncesByClient.Clear();
			_nextNonce = 0uL;
			CustomMessagingManager customMessagingManager = network.CustomMessagingManager;
			object obj = <>O.<0>__HandleBalance;
			if (obj == null)
			{
				HandleNamedMessageDelegate val = HandleBalance;
				<>O.<0>__HandleBalance = val;
				obj = (object)val;
			}
			customMessagingManager.RegisterNamedMessageHandler("timo.sharedlgucurrency/v1/balance", (HandleNamedMessageDelegate)obj);
			object obj2 = <>O.<1>__HandlePurchaseResult;
			if (obj2 == null)
			{
				HandleNamedMessageDelegate val2 = HandlePurchaseResult;
				<>O.<1>__HandlePurchaseResult = val2;
				obj2 = (object)val2;
			}
			customMessagingManager.RegisterNamedMessageHandler("timo.sharedlgucurrency/v1/purchase-result", (HandleNamedMessageDelegate)obj2);
			if (network.IsServer)
			{
				object obj3 = <>O.<2>__HandleDeltaRequest;
				if (obj3 == null)
				{
					HandleNamedMessageDelegate val3 = HandleDeltaRequest;
					<>O.<2>__HandleDeltaRequest = val3;
					obj3 = (object)val3;
				}
				customMessagingManager.RegisterNamedMessageHandler("timo.sharedlgucurrency/v1/delta", (HandleNamedMessageDelegate)obj3);
				object obj4 = <>O.<3>__HandlePurchaseRequest;
				if (obj4 == null)
				{
					HandleNamedMessageDelegate val4 = HandlePurchaseRequest;
					<>O.<3>__HandlePurchaseRequest = val4;
					obj4 = (object)val4;
				}
				customMessagingManager.RegisterNamedMessageHandler("timo.sharedlgucurrency/v1/purchase", (HandleNamedMessageDelegate)obj4);
				object obj5 = <>O.<4>__HandleSyncRequest;
				if (obj5 == null)
				{
					HandleNamedMessageDelegate val5 = HandleSyncRequest;
					<>O.<4>__HandleSyncRequest = val5;
					obj5 = (object)val5;
				}
				customMessagingManager.RegisterNamedMessageHandler("timo.sharedlgucurrency/v1/sync-request", (HandleNamedMessageDelegate)obj5);
			}
			network.OnClientConnectedCallback += HandleClientConnected;
			SharedCurrencyManager.Instance.BeginNetworkSession(network.IsServer);
			if (network.IsServer)
			{
				SharedCurrencyManager.Instance.HostInitializeIfNeeded();
			}
			else
			{
				RequestSync();
			}
			Plugin.Verbose("Network protocol registered as " + (network.IsServer ? "server" : "client") + ".");
		}

		private static void Unregister()
		{
			if (!((Object)(object)_registeredNetwork == (Object)null))
			{
				try
				{
					CustomMessagingManager customMessagingManager = _registeredNetwork.CustomMessagingManager;
					customMessagingManager.UnregisterNamedMessageHandler("timo.sharedlgucurrency/v1/balance");
					customMessagingManager.UnregisterNamedMessageHandler("timo.sharedlgucurrency/v1/purchase-result");
					customMessagingManager.UnregisterNamedMessageHandler("timo.sharedlgucurrency/v1/delta");
					customMessagingManager.UnregisterNamedMessageHandler("timo.sharedlgucurrency/v1/purchase");
					customMessagingManager.UnregisterNamedMessageHandler("timo.sharedlgucurrency/v1/sync-request");
					_registeredNetwork.OnClientConnectedCallback -= HandleClientConnected;
				}
				catch (Exception ex)
				{
					Plugin.Verbose("Network cleanup ignored: " + ex.Message);
				}
				_registeredNetwork = null;
				SeenNoncesByClient.Clear();
			}
		}

		internal unsafe static void RequestDelta(int delta, bool trackSpent, string source)
		{
			//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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: 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)
			if (!IsReady)
			{
				return;
			}
			if (IsServer)
			{
				SharedCurrencyManager.Instance.HostApplyDelta(delta, trackSpent, _registeredNetwork.LocalClientId, source, out string _);
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(512, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref delta, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref trackSpent, default(ForPrimitives));
				ulong num = ++_nextNonce;
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref num, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(source, false);
				_registeredNetwork.CustomMessagingManager.SendNamedMessage("timo.sharedlgucurrency/v1/delta", 0uL, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		internal unsafe static void RequestPurchase(string upgradeName, int expectedCurrencyPrice)
		{
			//IL_0058: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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)
			if (!IsReady)
			{
				LguReflection.ShowResult("SharedLGUCurrency", "Network is not ready; purchase was not sent.");
				return;
			}
			if (IsServer)
			{
				SharedCurrencyManager.Instance.HostHandlePurchase(_registeredNetwork.LocalClientId, upgradeName, expectedCurrencyPrice);
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(512, (Allocator)2, -1);
			try
			{
				ulong num = ++_nextNonce;
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref num, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(upgradeName, false);
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref expectedCurrencyPrice, default(ForPrimitives));
				_registeredNetwork.CustomMessagingManager.SendNamedMessage("timo.sharedlgucurrency/v1/purchase", 0uL, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		internal unsafe static void RequestSync()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (!IsReady || IsServer)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1);
			try
			{
				_registeredNetwork.CustomMessagingManager.SendNamedMessage("timo.sharedlgucurrency/v1/sync-request", 0uL, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		internal static void BroadcastBalance(int balance, int spent)
		{
			if (!IsServer)
			{
				return;
			}
			foreach (ulong connectedClientsId in _registeredNetwork.ConnectedClientsIds)
			{
				SendBalance(connectedClientsId, balance, spent);
			}
		}

		internal unsafe static void SendPurchaseResult(ulong clientId, bool success, string message)
		{
			//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_0041: Unknown result type (might be due to invalid IL or missing references)
			if (!IsServer)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(768, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref success, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(message, false);
				_registeredNetwork.CustomMessagingManager.SendNamedMessage("timo.sharedlgucurrency/v1/purchase-result", clientId, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void HandleDeltaRequest(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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_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)
			int delta = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref delta, default(ForPrimitives));
			bool trackSpent = default(bool);
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref trackSpent, default(ForPrimitives));
			ulong nonce = default(ulong);
			((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref nonce, default(ForPrimitives));
			string source = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref source, false);
			if (AcceptNonce(senderClientId, nonce) && !SharedCurrencyManager.Instance.HostApplyDelta(delta, trackSpent, senderClientId, source, out string rejection))
			{
				SendPurchaseResult(senderClientId, success: false, "Currency request rejected: " + rejection);
			}
		}

		private static void HandlePurchaseRequest(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			ulong nonce = default(ulong);
			((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref nonce, default(ForPrimitives));
			string upgradeName = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref upgradeName, false);
			int expectedPrice = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref expectedPrice, default(ForPrimitives));
			if (AcceptNonce(senderClientId, nonce))
			{
				SharedCurrencyManager.Instance.HostHandlePurchase(senderClientId, upgradeName, expectedPrice);
			}
		}

		private static void HandleSyncRequest(ulong senderClientId, FastBufferReader reader)
		{
			SharedCurrencyManager.Instance.HostInitializeIfNeeded();
			SendBalance(senderClientId, SharedCurrencyManager.Instance.Balance, SharedCurrencyManager.Instance.Spent);
			Plugin.Log.LogInfo((object)$"Late joiner synchronized: {SharedCurrencyManager.Instance.Balance} PC (client {senderClientId})");
		}

		private static void HandleBalance(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_registeredNetwork == (Object)null) && senderClientId == 0L)
			{
				int balance = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref balance, default(ForPrimitives));
				int spent = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref spent, default(ForPrimitives));
				SharedCurrencyManager.Instance.ApplyRemoteMirror(balance, spent);
			}
		}

		private static void HandlePurchaseResult(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_registeredNetwork == (Object)null) && senderClientId == 0L)
			{
				bool flag = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag, default(ForPrimitives));
				string message = default(string);
				((FastBufferReader)(ref reader)).ReadValueSafe(ref message, false);
				LguReflection.ShowResult(flag ? "Shared purchase approved" : "Shared purchase rejected", message);
			}
		}

		private static void HandleClientConnected(ulong clientId)
		{
			if (IsServer)
			{
				SendBalance(clientId, SharedCurrencyManager.Instance.Balance, SharedCurrencyManager.Instance.Spent);
			}
		}

		private unsafe static void SendBalance(ulong clientId, int balance, int spent)
		{
			//IL_0019: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			if (!IsServer)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(16, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref balance, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref spent, default(ForPrimitives));
				_registeredNetwork.CustomMessagingManager.SendNamedMessage("timo.sharedlgucurrency/v1/balance", clientId, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static bool AcceptNonce(ulong clientId, ulong nonce)
		{
			if (nonce == 0L)
			{
				return false;
			}
			if (!SeenNoncesByClient.TryGetValue(clientId, out HashSet<ulong> value))
			{
				value = new HashSet<ulong>();
				SeenNoncesByClient[clientId] = value;
			}
			if (!value.Add(nonce))
			{
				Plugin.Log.LogWarning((object)$"Rejected replayed request {nonce} from client {clientId}.");
				return false;
			}
			if (value.Count > 2048)
			{
				value.Clear();
				value.Add(nonce);
			}
			return true;
		}
	}
	[BepInPlugin("timo.sharedlgucurrency", "SharedLGUCurrency", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "timo.sharedlgucurrency";

		public const string PluginName = "SharedLGUCurrency";

		public const string PluginVersion = "1.0.0";

		public const string LguGuid = "com.malco.lethalcompany.moreshipupgrades";

		public const string MilkMolarsGuid = "Snowlance.MilkMolars";

		private Harmony? _harmony;

		internal static ManualLogSource Log { get; private set; }

		internal static ModConfig Settings { get; private set; }

		private void Awake()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			if (!Settings.Enabled.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"SharedLGUCurrency is disabled by config.");
				return;
			}
			SharedCurrencyManager.Initialize();
			MilkMolarsPatches.ApplyForcedConfig();
			_harmony = new Harmony("timo.sharedlgucurrency");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			GameObject val = new GameObject("SharedLGUCurrency.NetworkBridge");
			Object.DontDestroyOnLoad((Object)(object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<NetworkSyncManager>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SharedLGUCurrency 1.0.0 loaded for LGU 3.14.1 / MilkMolars 1.0.0.");
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		internal static void Verbose(string message)
		{
			if (Settings.VerboseLogging.Value)
			{
				Log.LogInfo((object)message);
			}
		}
	}
	internal sealed class SharedCurrencyManager
	{
		private readonly object _gate = new object();

		private readonly Dictionary<string, float> _purchaseLocks = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private int _balance;

		private int _spent;

		private bool _hostInitialized;

		internal static SharedCurrencyManager Instance { get; private set; }

		internal int Balance
		{
			get
			{
				lock (_gate)
				{
					return _balance;
				}
			}
		}

		internal int Spent
		{
			get
			{
				lock (_gate)
				{
					return _spent;
				}
			}
		}

		internal static void Initialize()
		{
			Instance = new SharedCurrencyManager();
		}

		internal void BeginNetworkSession(bool isServer)
		{
			lock (_gate)
			{
				_balance = 0;
				_spent = 0;
				_hostInitialized = false;
				_purchaseLocks.Clear();
			}
			LguReflection.MirrorBalance(0, 0);
			MilkMolarsPatches.ResetSession();
			if (isServer)
			{
				Plugin.Log.LogInfo((object)"Crew wallet initialized: 0 PC");
			}
		}

		internal void HostImportFromLgu(string reason)
		{
			if (NetworkSyncManager.IsServer)
			{
				int num = Math.Max(0, LguReflection.ReadBalance());
				int spent = LguReflection.ReadSpent();
				lock (_gate)
				{
					_balance = num;
					_spent = spent;
					_hostInitialized = true;
				}
				MirrorAndBroadcast();
				Plugin.Log.LogInfo((object)$"Crew wallet loaded from LGU ({reason}): {num} PC");
			}
		}

		internal void HostInitializeIfNeeded()
		{
			if (!NetworkSyncManager.IsServer)
			{
				return;
			}
			lock (_gate)
			{
				if (_hostInitialized)
				{
					return;
				}
				_balance = Math.Max(0, LguReflection.ReadBalance());
				_spent = LguReflection.ReadSpent();
				_hostInitialized = true;
			}
			MirrorAndBroadcast();
		}

		internal bool HostApplyDelta(int delta, bool trackSpent, ulong senderClientId, string source, out string rejection)
		{
			rejection = string.Empty;
			if (!NetworkSyncManager.IsServer)
			{
				rejection = "Only the host may mutate the crew wallet.";
				return false;
			}
			int value = Plugin.Settings.MaximumExternalDeltaPerRequest.Value;
			if (value > 0 && Math.Abs((long)delta) > value)
			{
				rejection = $"Currency delta {delta} exceeds the configured request limit ({value}).";
				Plugin.Log.LogWarning((object)$"Rejected currency request from client {senderClientId}: {rejection}");
				return false;
			}
			lock (_gate)
			{
				if (delta < 0 && _balance < -delta)
				{
					rejection = "Insufficient shared currency.";
					return false;
				}
				long val = (long)_balance + (long)delta;
				int maximumBalance = LguReflection.GetMaximumBalance();
				_balance = (int)Math.Max(0L, Math.Min(val, maximumBalance));
				if (trackSpent)
				{
					_spent = SafeInt((long)_spent - (long)delta);
				}
				_hostInitialized = true;
			}
			MirrorAndBroadcast();
			Plugin.Verbose($"Accepted wallet delta {delta:+#;-#;0} PC from client {senderClientId} ({source}). Shared balance: {Balance} PC");
			return true;
		}

		internal void HostCreditMolar(int amount, ulong playerId, string molarName)
		{
			if (amount > 0)
			{
				if (HostApplyDelta(amount, trackSpent: false, playerId, molarName, out string rejection))
				{
					Plugin.Log.LogInfo((object)$"Player {playerId} returned {molarName}: +{amount} PC");
					Plugin.Log.LogInfo((object)$"Shared balance: {Balance} PC");
				}
				else
				{
					Plugin.Log.LogWarning((object)$"Rejected {molarName} reward for player {playerId}: {rejection}");
				}
			}
		}

		internal void HostReset(string reason)
		{
			if (!NetworkSyncManager.IsServer)
			{
				return;
			}
			lock (_gate)
			{
				if (_balance == 0 && _spent == 0 && _hostInitialized)
				{
					return;
				}
				_balance = 0;
				_spent = 0;
				_hostInitialized = true;
				_purchaseLocks.Clear();
			}
			MirrorAndBroadcast();
			Plugin.Log.LogInfo((object)("Crew wallet reset (" + reason + "): 0 PC"));
		}

		internal void ApplyRemoteMirror(int balance, int spent)
		{
			lock (_gate)
			{
				_balance = Math.Max(0, balance);
				_spent = spent;
			}
			LguReflection.MirrorBalance(Balance, Spent);
			Plugin.Verbose($"Mirrored shared balance: {Balance} PC");
		}

		internal void HostHandlePurchase(ulong buyerClientId, string upgradeName, int expectedPrice)
		{
			if (!NetworkSyncManager.IsServer)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			lock (_gate)
			{
				if (_purchaseLocks.TryGetValue(upgradeName, out var value) && value > realtimeSinceStartup)
				{
					NetworkSyncManager.SendPurchaseResult(buyerClientId, success: false, "Another purchase for this upgrade is still being committed.");
					return;
				}
				_purchaseLocks[upgradeName] = realtimeSinceStartup + 0.25f;
			}
			if (!LguReflection.TryResolvePurchase(upgradeName, expectedPrice, out object node, out int actualCurrencyPrice, out bool increment, out string rejection))
			{
				RejectPurchase(buyerClientId, rejection);
				return;
			}
			int balance;
			int spent;
			lock (_gate)
			{
				if (_balance < actualCurrencyPrice)
				{
					RejectPurchase(buyerClientId, "Insufficient shared currency.");
					return;
				}
				balance = _balance;
				spent = _spent;
				_balance -= actualCurrencyPrice;
				_spent = SafeInt((long)_spent + (long)actualCurrencyPrice);
			}
			MirrorAndBroadcast();
			try
			{
				LguReflection.ApplySharedPurchase(node, increment);
				string displayName = LguReflection.GetDisplayName(node);
				Plugin.Log.LogInfo((object)$"Player {buyerClientId} purchased {displayName} for {actualCurrencyPrice} PC");
				Plugin.Log.LogInfo((object)$"Shared balance: {Balance} PC");
				NetworkSyncManager.SendPurchaseResult(buyerClientId, success: true, $"Purchased {displayName} for {actualCurrencyPrice} PC. Crew balance: {Balance} PC.");
			}
			catch (Exception arg)
			{
				lock (_gate)
				{
					_balance = balance;
					_spent = spent;
				}
				MirrorAndBroadcast();
				Plugin.Log.LogError((object)$"LGU purchase failed and was rolled back: {arg}");
				RejectPurchase(buyerClientId, "LGU failed to apply the upgrade; the shared currency deduction was rolled back.");
			}
		}

		private void RejectPurchase(ulong buyerClientId, string reason)
		{
			Plugin.Log.LogWarning((object)$"Purchase rejected for client {buyerClientId}: {reason}");
			NetworkSyncManager.SendPurchaseResult(buyerClientId, success: false, reason);
		}

		private void MirrorAndBroadcast()
		{
			int balance = Balance;
			int spent = Spent;
			LguReflection.MirrorBalance(balance, spent);
			NetworkSyncManager.BroadcastBalance(balance, spent);
		}

		private static int SafeInt(long value)
		{
			return (int)Math.Max(-2147483648L, Math.Min(2147483647L, value));
		}
	}
}