Decompiled source of EnergyCrystalAutoRefill Fix v4.0.1

EnergyCrystalAutoRefill Fix.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
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("Codex")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("4.0.1.0")]
[assembly: AssemblyInformationalVersion("4.0.1")]
[assembly: AssemblyProduct("EnergyCrystalShopStockFix")]
[assembly: AssemblyTitle("EnergyCrystalShopStockFix")]
[assembly: AssemblyVersion("4.0.1.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 REPOJP.EnergyCrystalShopStockFix
{
	[BepInPlugin("REPOJP.EnergyCrystalShopStockFix", "Energy Crystal Auto Refill - Shop Stock Fix", "4.0.1")]
	[BepInDependency("REPOJP.EnergyCrystalAutoRefill", "4.0.0")]
	public sealed class EnergyCrystalShopStockFixPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "REPOJP.EnergyCrystalShopStockFix";

		public const string PluginName = "Energy Crystal Auto Refill - Shop Stock Fix";

		public const string PluginVersion = "4.0.1";

		internal const string TargetPluginGuid = "REPOJP.EnergyCrystalAutoRefill";

		internal const string PowerCrystalItemName = "Item Power Crystal";

		internal const string ChargeTotalStatName = "chargingStationChargeTotal";

		internal const string SaveStateDictionaryName = "EnergyCrystalShopStockFix";

		private Harmony? _harmony;

		internal static ManualLogSource ModLogger { get; private set; }

		private void Awake()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			ModLogger = ((BaseUnityPlugin)this).Logger;
			VirtualCrystalTracker.InitializeRuntime();
			_harmony = new Harmony("REPOJP.EnergyCrystalShopStockFix");
			try
			{
				_harmony.PatchAll(typeof(ShopInventoryScopePatch));
				_harmony.PatchAll(typeof(CurrentPurchaseCountPatch));
				_harmony.PatchAll(typeof(TotalPurchaseCountPatch));
				_harmony.PatchAll(typeof(CrystalConsumedPatch));
				_harmony.PatchAll(typeof(NewRunPatch));
				_harmony.PatchAll(typeof(StatsManagerStartPatch));
				_harmony.PatchAll(typeof(LoadGamePatch));
				PatchAutoRefillMethod(_harmony);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Shop stock fix loaded for EnergyCrystalAutoRefill 4.0.0.");
			}
			catch (Exception arg)
			{
				_harmony.UnpatchSelf();
				((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to apply shop stock fix: {arg}");
			}
		}

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

		private static void PatchAutoRefillMethod(Harmony harmony)
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Expected O, but got Unknown
			//IL_00e0: Expected O, but got Unknown
			if (!Chainloader.PluginInfos.TryGetValue("REPOJP.EnergyCrystalAutoRefill", out var value))
			{
				throw new InvalidOperationException("Required plugin 'REPOJP.EnergyCrystalAutoRefill' is not loaded.");
			}
			Type type = AccessTools.Inner(((object)value.Instance).GetType(), "ChargingStationUpdatePostfixPatch");
			if ((object)type == null)
			{
				throw new MissingMemberException(((object)value.Instance).GetType().FullName, "ChargingStationUpdatePostfixPatch");
			}
			MethodInfo methodInfo = AccessTools.Method(type, "TryAddPowerCrystalPurchase", new Type[3]
			{
				typeof(ChargingStation),
				typeof(int),
				typeof(int)
			}, (Type[])null);
			if ((object)methodInfo == null)
			{
				throw new MissingMethodException(type.FullName, "TryAddPowerCrystalPurchase(ChargingStation, int, int)");
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(AutoRefillTrackingPatch), "Prefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(AutoRefillTrackingPatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}
	internal readonly struct PurchaseCountSnapshot
	{
		internal int Current { get; }

		internal int Total { get; }

		internal PurchaseCountSnapshot(int current, int total)
		{
			Current = current;
			Total = total;
		}

		internal static PurchaseCountSnapshot Capture()
		{
			StatsManager instance = StatsManager.instance;
			if (instance == null)
			{
				return default(PurchaseCountSnapshot);
			}
			instance.itemsPurchased.TryGetValue("Item Power Crystal", out var value);
			instance.itemsPurchasedTotal.TryGetValue("Item Power Crystal", out var value2);
			return new PurchaseCountSnapshot(value, value2);
		}
	}
	internal static class VirtualCrystalTracker
	{
		private const string RecordVersionKey = "RecordVersion";

		private const string CurrentOffsetKey = "CurrentAutoRefillCrystals";

		private const string TotalOffsetKey = "TotalAutoRefillCrystals";

		private const int CurrentRecordVersion = 1;

		private static readonly Dictionary<string, int> SaveState = new Dictionary<string, int>();

		private static readonly FieldInfo DictionaryRegistryField = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");

		private static readonly FieldInfo EnergyPerCrystalField = AccessTools.Field(typeof(ChargingStation), "energyPerCrystal");

		private static readonly FieldInfo MaxCrystalsField = AccessTools.Field(typeof(ChargingStation), "maxCrystals");

		private static int _currentAutoRefillCrystals;

		private static int _totalAutoRefillCrystals;

		private static int _shopInventoryQueryDepth;

		private static bool _legacyRepairPending;

		internal static bool IsShopInventoryQuery => _shopInventoryQueryDepth > 0;

		internal static void InitializeRuntime()
		{
			SaveState.Clear();
			_currentAutoRefillCrystals = 0;
			_totalAutoRefillCrystals = 0;
			_shopInventoryQueryDepth = 0;
			_legacyRepairPending = false;
		}

		internal static void RegisterSaveState(StatsManager stats)
		{
			if (!(DictionaryRegistryField.GetValue(stats) is SortedDictionary<string, Dictionary<string, int>> sortedDictionary))
			{
				throw new InvalidOperationException("StatsManager dictionary registry is unavailable.");
			}
			if (sortedDictionary.TryGetValue("EnergyCrystalShopStockFix", out var value))
			{
				if (value == SaveState)
				{
					return;
				}
				SaveState.Clear();
				foreach (KeyValuePair<string, int> item in value)
				{
					SaveState[item.Key] = item.Value;
				}
				sortedDictionary["EnergyCrystalShopStockFix"] = SaveState;
			}
			else
			{
				sortedDictionary.Add("EnergyCrystalShopStockFix", SaveState);
			}
		}

		internal static void BeginLoad(StatsManager stats)
		{
			RegisterSaveState(stats);
			SaveState.Clear();
			_currentAutoRefillCrystals = 0;
			_totalAutoRefillCrystals = 0;
			_shopInventoryQueryDepth = 0;
			_legacyRepairPending = false;
		}

		internal static void FinishLoad()
		{
			if (SaveState.TryGetValue("RecordVersion", out var value) && value > 0)
			{
				_currentAutoRefillCrystals = GetSavedNonNegative("CurrentAutoRefillCrystals");
				_totalAutoRefillCrystals = GetSavedNonNegative("TotalAutoRefillCrystals");
				_legacyRepairPending = false;
				EnergyCrystalShopStockFixPlugin.ModLogger.LogDebug((object)($"Loaded per-save refill offsets: current={_currentAutoRefillCrystals}, " + $"total={_totalAutoRefillCrystals}."));
			}
			else
			{
				_currentAutoRefillCrystals = 0;
				_totalAutoRefillCrystals = 0;
				_legacyRepairPending = true;
				EnergyCrystalShopStockFixPlugin.ModLogger.LogInfo((object)"Loaded a save without shop-stock-fix records. Legacy repair will run before the shop inventory is generated.");
			}
		}

		internal static void NormalizePurchaseCountForShop()
		{
			StatsManager instance = StatsManager.instance;
			ChargingStation instance2 = ChargingStation.instance;
			if (instance == null || instance2 == null || !instance.itemDictionary.TryGetValue("Item Power Crystal", out var value))
			{
				return;
			}
			int num = Math.Max(0, value.maxAmountInShop);
			instance.itemsPurchased.TryGetValue("Item Power Crystal", out var value2);
			bool flag = false;
			if (value2 >= num)
			{
				instance.runStats.TryGetValue("chargingStationChargeTotal", out var value3);
				int num2 = Math.Max(1, (int)EnergyPerCrystalField.GetValue(instance2));
				int num3 = Math.Max(0, (int)MaxCrystalsField.GetValue(instance2));
				int num4 = Mathf.Clamp(Mathf.CeilToInt((float)value3 / (float)num2), 0, num3);
				int num5 = num4;
				int val = Math.Max(0, value2 - num5);
				if (num5 != value2)
				{
					instance.SetItemPurchase(value, num5);
					if ((Object)(object)PunManager.instance != (Object)null && SemiFunc.IsMasterClientOrSingleplayer())
					{
						PunManager.instance.UpdateStat("itemsPurchased", "Item Power Crystal", num5);
					}
					flag = true;
				}
				_currentAutoRefillCrystals = 0;
				if (_legacyRepairPending)
				{
					_totalAutoRefillCrystals = Math.Max(_totalAutoRefillCrystals, val);
				}
				EnergyCrystalShopStockFixPlugin.ModLogger.LogWarning((object)($"Normalized Power Crystal purchase count for shop: {value2} -> " + $"{num5}. Remaining charge={value3}, " + $"rendered crystals={num4}, " + $"energyPerCrystal={num2}, maxCrystals={num3}, " + $"effective maxAmountInShop={num}."));
			}
			else if (_legacyRepairPending)
			{
				EnergyCrystalShopStockFixPlugin.ModLogger.LogInfo((object)("Legacy save did not require repair. Power Crystal purchases=" + $"{value2}, effective maxAmountInShop={num}."));
			}
			if (_legacyRepairPending || flag)
			{
				_legacyRepairPending = false;
				Persist();
			}
		}

		internal static void RecordRefill(PurchaseCountSnapshot before, PurchaseCountSnapshot after)
		{
			int num = Math.Max(0, after.Current - before.Current);
			int num2 = Math.Max(0, after.Total - before.Total);
			_currentAutoRefillCrystals += num;
			_totalAutoRefillCrystals += num2;
			Persist();
			if (num > 0 || num2 > 0)
			{
				EnergyCrystalShopStockFixPlugin.ModLogger.LogDebug((object)($"Tracked auto-refill crystals: current +{num}, total +{num2}. " + $"Offsets are now current={_currentAutoRefillCrystals}, total={_totalAutoRefillCrystals}."));
			}
		}

		internal static void RecordConsumption(PurchaseCountSnapshot before, PurchaseCountSnapshot after)
		{
			int num = Math.Max(0, before.Current - after.Current);
			if (num != 0)
			{
				_currentAutoRefillCrystals = Math.Max(0, _currentAutoRefillCrystals - num);
				Persist();
			}
		}

		internal static int RemoveCurrentAutoRefills(int purchaseCount)
		{
			return Math.Max(0, purchaseCount - _currentAutoRefillCrystals);
		}

		internal static int RemoveTotalAutoRefills(int purchaseCount)
		{
			return Math.Max(0, purchaseCount - _totalAutoRefillCrystals);
		}

		internal static void BeginShopInventoryQuery()
		{
			_shopInventoryQueryDepth++;
		}

		internal static void EndShopInventoryQuery()
		{
			_shopInventoryQueryDepth = Math.Max(0, _shopInventoryQueryDepth - 1);
		}

		internal static void Reset()
		{
			_currentAutoRefillCrystals = 0;
			_totalAutoRefillCrystals = 0;
			_shopInventoryQueryDepth = 0;
			_legacyRepairPending = false;
			Persist();
		}

		internal static void ClearTransientState()
		{
			_shopInventoryQueryDepth = 0;
		}

		private static void Persist()
		{
			SaveState["RecordVersion"] = 1;
			SaveState["CurrentAutoRefillCrystals"] = Math.Max(0, _currentAutoRefillCrystals);
			SaveState["TotalAutoRefillCrystals"] = Math.Max(0, _totalAutoRefillCrystals);
		}

		private static int GetSavedNonNegative(string key)
		{
			if (!SaveState.TryGetValue(key, out var value))
			{
				return 0;
			}
			return Math.Max(0, value);
		}
	}
	internal static class AutoRefillTrackingPatch
	{
		internal static void Prefix(out PurchaseCountSnapshot __state)
		{
			__state = PurchaseCountSnapshot.Capture();
		}

		internal static void Postfix(PurchaseCountSnapshot __state)
		{
			VirtualCrystalTracker.RecordRefill(__state, PurchaseCountSnapshot.Capture());
		}
	}
	[HarmonyPatch(typeof(ShopManager), "GetAllItemsFromStatsManager")]
	internal static class ShopInventoryScopePatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			VirtualCrystalTracker.NormalizePurchaseCountForShop();
			VirtualCrystalTracker.BeginShopInventoryQuery();
		}

		[HarmonyFinalizer]
		private static Exception? Finalizer(Exception? __exception)
		{
			VirtualCrystalTracker.EndShopInventoryQuery();
			return __exception;
		}
	}
	[HarmonyPatch(typeof(SemiFunc), "StatGetItemsPurchased", new Type[] { typeof(string) })]
	internal static class CurrentPurchaseCountPatch
	{
		[HarmonyPostfix]
		private static void Postfix(string __0, ref int __result)
		{
			if (VirtualCrystalTracker.IsShopInventoryQuery && string.Equals(__0, "Item Power Crystal", StringComparison.Ordinal))
			{
				__result = VirtualCrystalTracker.RemoveCurrentAutoRefills(__result);
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "GetItemsUpgradesPurchasedTotal", new Type[] { typeof(string) })]
	internal static class TotalPurchaseCountPatch
	{
		[HarmonyPostfix]
		private static void Postfix(string __0, ref int __result)
		{
			if (VirtualCrystalTracker.IsShopInventoryQuery && string.Equals(__0, "Item Power Crystal", StringComparison.Ordinal))
			{
				__result = VirtualCrystalTracker.RemoveTotalAutoRefills(__result);
			}
		}
	}
	[HarmonyPatch(typeof(ChargingStation), "ChargingStationCrystalBrokenRPC")]
	internal static class CrystalConsumedPatch
	{
		[HarmonyPrefix]
		private static void Prefix(out PurchaseCountSnapshot __state)
		{
			__state = PurchaseCountSnapshot.Capture();
		}

		[HarmonyPostfix]
		private static void Postfix(PurchaseCountSnapshot __state)
		{
			VirtualCrystalTracker.RecordConsumption(__state, PurchaseCountSnapshot.Capture());
		}
	}
	[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
	internal static class NewRunPatch
	{
		[HarmonyPostfix]
		private static void Postfix(StatsManager __instance)
		{
			VirtualCrystalTracker.RegisterSaveState(__instance);
			VirtualCrystalTracker.Reset();
		}
	}
	[HarmonyPatch(typeof(StatsManager), "Start")]
	internal static class StatsManagerStartPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(StatsManager __instance)
		{
			VirtualCrystalTracker.RegisterSaveState(__instance);
		}
	}
	[HarmonyPatch(typeof(StatsManager), "LoadGame", new Type[]
	{
		typeof(string),
		typeof(List<string>)
	})]
	internal static class LoadGamePatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix(StatsManager __instance)
		{
			VirtualCrystalTracker.BeginLoad(__instance);
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			VirtualCrystalTracker.FinishLoad();
		}
	}
}