Decompiled source of Big Cart Fix v1.0.5

BigCartFix.dll

Decompiled 5 hours ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace BigCartFix;

[BepInPlugin("bigcart.fix.runtime", "Big Cart Fix", "1.0.5")]
public sealed class Plugin : BaseUnityPlugin
{
	internal const string PluginGuid = "bigcart.fix.runtime";

	internal const string PluginName = "Big Cart Fix";

	internal const string PluginVersion = "1.0.5";

	private void Awake()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		ModLog.Source = ((BaseUnityPlugin)this).Logger;
		ModConfig.Bind(((BaseUnityPlugin)this).Config);
		new Harmony("bigcart.fix.runtime").PatchAll();
		CartPurchaseSettings.Apply();
		ModLog.Info("Patched Big Cart display, null guard, and shop purchase limits.");
	}
}
internal static class ModConfig
{
	private const string DisplaySection = "Display";

	private const string ShopSection = "Shop";

	private const string GeneralSection = "General";

	private static ConfigEntry<bool> debugLogging;

	private static ConfigEntry<int> maxBigCartsForPurchase;

	private static ConfigEntry<string> valueTextPath;

	public static bool DebugLogging
	{
		get
		{
			if (debugLogging != null)
			{
				return debugLogging.Value;
			}
			return false;
		}
	}

	public static int MaxBigCartsForPurchase
	{
		get
		{
			if (maxBigCartsForPurchase == null)
			{
				return 4;
			}
			return Mathf.Clamp(maxBigCartsForPurchase.Value, 1, 20);
		}
	}

	public static string ValueTextPath
	{
		get
		{
			if (valueTextPath == null || valueTextPath.Value == null)
			{
				return string.Empty;
			}
			return valueTextPath.Value.Trim();
		}
	}

	public static void Bind(ConfigFile config)
	{
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		debugLogging = config.Bind<bool>("General", "DebugLogging", false, "Enable extra logs for Big Cart Fix.");
		valueTextPath = config.Bind<string>("Display", "ValueTextPath", string.Empty, "Optional Transform path to the Big Cart TMP value text. Leave empty to use the built-in paths.");
		maxBigCartsForPurchase = config.Bind<int>("Shop", "MaxBigCartsForPurchase", 4, new ConfigDescription("Maximum amount of Big Carts that can be offered/purchased during a run.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), new object[0]));
	}
}
internal static class ModLog
{
	public static ManualLogSource Source;

	public static void Info(string message)
	{
		if (Source != null)
		{
			Source.LogInfo((object)message);
		}
	}

	public static void Warning(string message)
	{
		if (Source != null)
		{
			Source.LogWarning((object)message);
		}
		else
		{
			Debug.LogWarning((object)("[Big Cart Fix] " + message));
		}
	}

	public static void DebugInfo(string message)
	{
		if (ModConfig.DebugLogging && Source != null)
		{
			Source.LogInfo((object)("[Debug] " + message));
		}
	}
}
internal static class CartValueDisplay
{
	private static readonly FieldInfo HaulCurrentField = AccessTools.Field(typeof(PhysGrabCart), "haulCurrent");

	private static readonly FieldInfo OriginalColorField = AccessTools.Field(typeof(ValueScreen), "originalColor");

	private static readonly FieldInfo ValuePreviousField = AccessTools.Field(typeof(ValueScreen), "valuePrevious");

	private static readonly FieldInfo ValueCurrentField = AccessTools.Field(typeof(ValueScreen), "valueCurrent");

	private static readonly FieldInfo ResetTextField = AccessTools.Field(typeof(ValueScreen), "resetText");

	private static readonly FieldInfo SoundSourceField = AccessTools.Field(typeof(Sound), "Source");

	private static readonly FieldInfo[] SoundFields = typeof(Sound).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly string[] ValueTextPathCandidates = new string[12]
	{
		"Value Screen/Value Text", "Value Screen/Text", "Value Screen/Text (TMP)", "ValueScreen/ValueText", "ValueScreen/Text", "Screen/Value Text", "Screen/Text", "Canvas/Value Text", "Canvas/Text", "Canvas/Text (TMP)",
		"Value Text", "Text (TMP)"
	};

	private static readonly Dictionary<int, TMP_Text> FallbackTexts = new Dictionary<int, TMP_Text>();

	private static readonly HashSet<int> BoundScreens = new HashSet<int>();

	private static readonly HashSet<int> MissingDisplayWarnings = new HashSet<int>();

	private static readonly HashSet<int> LoggedTextPaths = new HashSet<int>();

	private static Sound sourceIncreaseSound;

	private static Sound sourceDecreaseSound;

	private static bool loggedSoundAttached;

	private static bool loggedSoundMissing;

	public static void EnsureBound(PhysGrabCart cart)
	{
		if ((Object)(object)cart == (Object)null)
		{
			return;
		}
		int instanceID = ((Object)cart).GetInstanceID();
		if (FallbackTexts.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null)
		{
			return;
		}
		LogTextPaths(cart);
		if ((Object)(object)cart.valueScreen != (Object)null && (Object)(object)cart.valueScreen.displayText != (Object)null)
		{
			BindScreen(cart, cart.valueScreen, cart.valueScreen.displayText);
			return;
		}
		ValueScreen componentInChildren = ((Component)cart).GetComponentInChildren<ValueScreen>(true);
		TMP_Text val = FindDisplayText(cart, componentInChildren);
		TextMeshPro val2 = (TextMeshPro)(object)((val is TextMeshPro) ? val : null);
		if ((Object)(object)componentInChildren != (Object)null)
		{
			if ((Object)(object)componentInChildren.displayText == (Object)null && (Object)(object)val2 != (Object)null)
			{
				componentInChildren.displayText = val2;
			}
			if ((Object)(object)componentInChildren.displayText != (Object)null)
			{
				BindScreen(cart, componentInChildren, componentInChildren.displayText);
				return;
			}
		}
		if ((Object)(object)val2 != (Object)null)
		{
			ValueScreen val3 = ((Component)val2).gameObject.GetComponent<ValueScreen>();
			if ((Object)(object)val3 == (Object)null)
			{
				val3 = ((Component)val2).gameObject.AddComponent<ValueScreen>();
			}
			BindScreen(cart, val3, val2);
		}
		else if ((Object)(object)val != (Object)null)
		{
			FallbackTexts[instanceID] = val;
			SetText(val, ReadHaul(cart));
		}
		else if (MissingDisplayWarnings.Add(instanceID))
		{
			ModLog.Warning("Could not find a value text display on this cart prefab.");
		}
	}

	private static void BindScreen(PhysGrabCart cart, ValueScreen screen, TextMeshPro displayText)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)cart == (Object)null) && !((Object)(object)screen == (Object)null) && !((Object)(object)displayText == (Object)null))
		{
			int instanceID = ((Object)cart).GetInstanceID();
			screen.displayText = displayText;
			cart.valueScreen = screen;
			OriginalColorField.SetValue(screen, ((Graphic)displayText).color);
			TryAttachSounds(screen);
			if (BoundScreens.Add(instanceID))
			{
				SetScreenBaseline(screen, ReadHaul(cart));
			}
		}
	}

	private static bool TryAttachSounds(ValueScreen screen)
	{
		if ((Object)(object)screen == (Object)null)
		{
			return false;
		}
		CacheSourceSounds(screen);
		if (!HasPlayableSound(screen.soundValueIncrease) && HasPlayableSound(sourceIncreaseSound))
		{
			screen.soundValueIncrease = CloneSound(sourceIncreaseSound);
		}
		if (!HasPlayableSound(screen.soundValueDecrease) && HasPlayableSound(sourceDecreaseSound))
		{
			screen.soundValueDecrease = CloneSound(sourceDecreaseSound);
		}
		bool flag = HasPlayableSound(screen.soundValueIncrease) && HasPlayableSound(screen.soundValueDecrease);
		if (flag && !loggedSoundAttached)
		{
			ModLog.Info("Attached vanilla cart value sounds to Big Cart value display.");
			loggedSoundAttached = true;
		}
		return flag;
	}

	private static void CacheSourceSounds(ValueScreen skipScreen)
	{
		if (HasPlayableSound(sourceIncreaseSound) && HasPlayableSound(sourceDecreaseSound))
		{
			return;
		}
		ValueScreen[] array = Resources.FindObjectsOfTypeAll<ValueScreen>();
		ValueScreen[] array2 = array;
		foreach (ValueScreen val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)skipScreen) && HasPlayableSound(val.soundValueIncrease) && HasPlayableSound(val.soundValueDecrease))
			{
				sourceIncreaseSound = val.soundValueIncrease;
				sourceDecreaseSound = val.soundValueDecrease;
				break;
			}
		}
	}

	private static bool HasPlayableSound(Sound sound)
	{
		if (sound != null && sound.Sounds != null)
		{
			return sound.Sounds.Length > 0;
		}
		return false;
	}

	private static Sound CloneSound(Sound source)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		if (source == null)
		{
			return null;
		}
		Sound val = new Sound();
		FieldInfo[] soundFields = SoundFields;
		foreach (FieldInfo fieldInfo in soundFields)
		{
			if (!fieldInfo.IsInitOnly && !(fieldInfo.Name == "Source") && !(fieldInfo.Name == "LowPassLogic") && !(fieldInfo.Name == "HasLowPassLogic"))
			{
				fieldInfo.SetValue(val, fieldInfo.GetValue(source));
			}
		}
		SoundSourceField.SetValue(val, null);
		return val;
	}

	private static void SetScreenBaseline(ValueScreen screen, int value)
	{
		int num = Mathf.Max(0, value);
		ValuePreviousField.SetValue(screen, num);
		ValueCurrentField.SetValue(screen, num);
		ResetTextField.SetValue(screen, true);
		SetText((TMP_Text)(object)screen.displayText, num);
	}

	public static void Refresh(PhysGrabCart cart)
	{
		EnsureBound(cart);
		if ((Object)(object)cart != (Object)null && FallbackTexts.TryGetValue(((Object)cart).GetInstanceID(), out var value) && (Object)(object)value != (Object)null)
		{
			SetText(value, ReadHaul(cart));
		}
	}

	public static bool TryHandleValueScreenUpdate(ValueScreen screen, int value)
	{
		if ((Object)(object)screen == (Object)null)
		{
			return false;
		}
		if ((Object)(object)screen.displayText == (Object)null)
		{
			return false;
		}
		if (TryAttachSounds(screen))
		{
			return true;
		}
		if (!loggedSoundMissing)
		{
			ModLog.Warning("No playable vanilla cart value sounds found yet; updating Big Cart value display without audio.");
			loggedSoundMissing = true;
		}
		SetText((TMP_Text)(object)screen.displayText, value);
		return false;
	}

	private static int ReadHaul(PhysGrabCart cart)
	{
		object value = HaulCurrentField.GetValue(cart);
		if (value is int)
		{
			return (int)value;
		}
		return 0;
	}

	private static TMP_Text FindDisplayText(PhysGrabCart cart, ValueScreen existingScreen)
	{
		if ((Object)(object)existingScreen != (Object)null && (Object)(object)existingScreen.displayText != (Object)null)
		{
			return (TMP_Text)(object)existingScreen.displayText;
		}
		TMP_Text val = FindTextAtPath(cart, ModConfig.ValueTextPath);
		if ((Object)(object)val != (Object)null)
		{
			ModLog.DebugInfo("Found value text from config path: " + GetPath(((Component)cart).transform, val.transform));
			return val;
		}
		string[] valueTextPathCandidates = ValueTextPathCandidates;
		foreach (string path in valueTextPathCandidates)
		{
			TMP_Text val2 = FindTextAtPath(cart, path);
			if ((Object)(object)val2 != (Object)null)
			{
				ModLog.DebugInfo("Found value text from built-in path: " + GetPath(((Component)cart).transform, val2.transform));
				return val2;
			}
		}
		TMP_Text val3 = FindTextByInitialValue(cart);
		if ((Object)(object)val3 != (Object)null)
		{
			ModLog.DebugInfo("Found value text by initial value: " + GetPath(((Component)cart).transform, val3.transform));
			return val3;
		}
		return FindOnlyWorldText(cart);
	}

	private static TMP_Text FindTextAtPath(PhysGrabCart cart, string path)
	{
		if ((Object)(object)cart == (Object)null || string.IsNullOrEmpty(path))
		{
			return null;
		}
		Transform val = FindTransformByPath(((Component)cart).transform, path);
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		TMP_Text component = ((Component)val).GetComponent<TMP_Text>();
		if ((Object)(object)component != (Object)null)
		{
			return component;
		}
		return ((Component)val).GetComponentInChildren<TMP_Text>(true);
	}

	private static Transform FindTransformByPath(Transform root, string path)
	{
		if ((Object)(object)root == (Object)null || string.IsNullOrEmpty(path))
		{
			return null;
		}
		string text = path.Trim().Replace('\\', '/').Trim(new char[1] { '/' });
		if (text.Length == 0)
		{
			return null;
		}
		if (string.Equals(text, ((Object)root).name, StringComparison.OrdinalIgnoreCase))
		{
			return root;
		}
		string text2 = ((Object)root).name + "/";
		if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase))
		{
			text = text.Substring(text2.Length);
		}
		Transform val = root.Find(text);
		if ((Object)(object)val != (Object)null)
		{
			return val;
		}
		return FindTransformByPathIgnoreCase(root, text);
	}

	private static Transform FindTransformByPathIgnoreCase(Transform root, string path)
	{
		string[] array = path.Split(new char[1] { '/' });
		Transform val = root;
		string[] array2 = array;
		foreach (string text in array2)
		{
			string text2 = text.Trim();
			if (text2.Length == 0)
			{
				continue;
			}
			Transform val2 = null;
			for (int j = 0; j < val.childCount; j++)
			{
				Transform child = val.GetChild(j);
				if (string.Equals(((Object)child).name, text2, StringComparison.OrdinalIgnoreCase))
				{
					val2 = child;
					break;
				}
			}
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			val = val2;
		}
		return val;
	}

	private static TMP_Text FindTextByInitialValue(PhysGrabCart cart)
	{
		TMP_Text[] componentsInChildren = ((Component)cart).GetComponentsInChildren<TMP_Text>(true);
		TMP_Text[] array = componentsInChildren;
		foreach (TMP_Text val in array)
		{
			if (!((Object)(object)val == (Object)null) && val.text != null && val.text.Contains("100000"))
			{
				return val;
			}
		}
		return null;
	}

	private static TMP_Text FindOnlyWorldText(PhysGrabCart cart)
	{
		TMP_Text[] componentsInChildren = ((Component)cart).GetComponentsInChildren<TMP_Text>(true);
		TMP_Text val = null;
		int num = 0;
		TMP_Text[] array = componentsInChildren;
		foreach (TMP_Text val2 in array)
		{
			if (val2 is TextMeshPro)
			{
				val = val2;
				num++;
			}
		}
		if (num == 1)
		{
			ModLog.DebugInfo("Found the only world TMP text: " + GetPath(((Component)cart).transform, val.transform));
			return val;
		}
		return null;
	}

	private static void LogTextPaths(PhysGrabCart cart)
	{
		if (!ModConfig.DebugLogging || (Object)(object)cart == (Object)null)
		{
			return;
		}
		int instanceID = ((Object)cart).GetInstanceID();
		if (!LoggedTextPaths.Add(instanceID))
		{
			return;
		}
		TMP_Text[] componentsInChildren = ((Component)cart).GetComponentsInChildren<TMP_Text>(true);
		TMP_Text[] array = componentsInChildren;
		foreach (TMP_Text val in array)
		{
			if (!((Object)(object)val == (Object)null))
			{
				string text = ((val.text == null) ? string.Empty : val.text);
				ModLog.DebugInfo("TMP path: " + GetPath(((Component)cart).transform, val.transform) + " | text: " + text);
			}
		}
	}

	private static string GetPath(Transform root, Transform target)
	{
		if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)null)
		{
			return string.Empty;
		}
		List<string> list = new List<string>();
		Transform val = target;
		while ((Object)(object)val != (Object)null)
		{
			list.Insert(0, ((Object)val).name);
			if ((Object)(object)val == (Object)(object)root)
			{
				break;
			}
			val = val.parent;
		}
		return string.Join("/", list.ToArray());
	}

	private static void SetText(TMP_Text text, int value)
	{
		int num = Mathf.Max(0, value);
		text.text = "<color=#bd4300>$</color>" + SemiFunc.DollarGetString(num);
	}
}
internal static class CartPurchaseSettings
{
	private static readonly string[] BigCartItemNames = new string[4] { "Cart Big", "Big Cart", "B.I.G. C.A.R.T.", "Big_Cart" };

	private static readonly HashSet<int> AppliedItems = new HashSet<int>();

	public static void Apply()
	{
		if ((Object)(object)StatsManager.instance == (Object)null || StatsManager.instance.itemDictionary == null)
		{
			return;
		}
		foreach (KeyValuePair<string, Item> item in StatsManager.instance.itemDictionary)
		{
			if (IsBigCartItem(item.Key, item.Value))
			{
				ApplyToItem(item.Value);
			}
		}
	}

	private static bool IsBigCartItem(string dictionaryKey, Item item)
	{
		if ((Object)(object)item == (Object)null)
		{
			return false;
		}
		if (!MatchesKnownName(dictionaryKey) && !MatchesKnownName(((Object)item).name))
		{
			return MatchesKnownName(item.itemName);
		}
		return true;
	}

	private static bool MatchesKnownName(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return false;
		}
		string a = value.Replace("(Clone)", string.Empty).Trim();
		string[] bigCartItemNames = BigCartItemNames;
		foreach (string b in bigCartItemNames)
		{
			if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}

	private static void ApplyToItem(Item item)
	{
		int num = (item.maxPurchaseAmount = (item.maxAmountInShop = (item.maxAmount = ModConfig.MaxBigCartsForPurchase)));
		if (AppliedItems.Add(((Object)item).GetInstanceID()))
		{
			ModLog.Info("Big Cart shop limit set to " + num + ".");
		}
		else
		{
			ModLog.DebugInfo("Big Cart shop limit refreshed to " + num + ".");
		}
	}
}
[HarmonyPatch(typeof(StatsManager), "LoadItemsFromFolder")]
internal static class StatsManagerLoadItemsFromFolderPatch
{
	private static void Postfix()
	{
		CartPurchaseSettings.Apply();
	}
}
[HarmonyPatch(typeof(ShopManager), "GetAllItemsFromStatsManager")]
internal static class ShopManagerGetAllItemsFromStatsManagerPatch
{
	private static void Prefix()
	{
		CartPurchaseSettings.Apply();
	}
}
[HarmonyPatch(typeof(ItemManager), "GetPurchasedItems")]
internal static class ItemManagerGetPurchasedItemsPatch
{
	private static void Prefix()
	{
		CartPurchaseSettings.Apply();
	}
}
[HarmonyPatch(typeof(PhysGrabCart), "Start")]
internal static class PhysGrabCartStartPatch
{
	private static void Postfix(PhysGrabCart __instance)
	{
		CartValueDisplay.EnsureBound(__instance);
	}
}
[HarmonyPatch(typeof(PhysGrabCart), "ObjectsInCart")]
internal static class PhysGrabCartObjectsInCartPatch
{
	private static readonly FieldInfo InCartField = AccessTools.Field(typeof(PhysGrabCart), "inCart");

	private static readonly HashSet<int> MissingInCartWarnings = new HashSet<int>();

	private static bool Prefix(PhysGrabCart __instance)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		CartValueDisplay.EnsureBound(__instance);
		Transform val = (Transform)InCartField.GetValue(__instance);
		if ((Object)(object)val != (Object)null)
		{
			return true;
		}
		int instanceID = ((Object)__instance).GetInstanceID();
		if (MissingInCartWarnings.Add(instanceID))
		{
			ModLog.Warning("Skipping cart item scan because this cart prefab has no 'In Cart' transform.");
		}
		return false;
	}

	private static void Postfix(PhysGrabCart __instance)
	{
		CartValueDisplay.Refresh(__instance);
	}

	private static Exception Finalizer(Exception __exception)
	{
		if (__exception is NullReferenceException)
		{
			return null;
		}
		return __exception;
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
	{
		FieldInfo valueScreenField = AccessTools.Field(typeof(PhysGrabCart), "valueScreen");
		FieldInfo haulCurrentField = AccessTools.Field(typeof(PhysGrabCart), "haulCurrent");
		MethodInfo updateValueMethod = AccessTools.Method(typeof(ValueScreen), "UpdateValue", new Type[1] { typeof(int) }, (Type[])null);
		List<CodeInstruction> codes = new List<CodeInstruction>(instructions);
		bool patched = false;
		for (int i = 0; i < codes.Count; i++)
		{
			CodeInstruction code = codes[i];
			if (!patched && CodeInstructionExtensions.LoadsField(code, valueScreenField, false) && i + 3 < codes.Count && codes[i + 1].opcode == OpCodes.Ldarg_0 && CodeInstructionExtensions.LoadsField(codes[i + 2], haulCurrentField, false) && CodeInstructionExtensions.Calls(codes[i + 3], updateValueMethod))
			{
				Label hasValueScreenLabel = generator.DefineLabel();
				codes[i + 1].labels.Add(hasValueScreenLabel);
				yield return code;
				yield return new CodeInstruction(OpCodes.Dup, (object)null);
				yield return new CodeInstruction(OpCodes.Brtrue_S, (object)hasValueScreenLabel);
				yield return new CodeInstruction(OpCodes.Pop, (object)null);
				yield return new CodeInstruction(OpCodes.Ret, (object)null);
				patched = true;
			}
			else
			{
				yield return code;
			}
		}
		if (!patched)
		{
			ModLog.Warning("Could not find ValueScreen.UpdateValue in PhysGrabCart.ObjectsInCart.");
		}
	}
}
[HarmonyPatch(typeof(ValueScreen), "UpdateValue")]
internal static class ValueScreenUpdateValuePatch
{
	private static bool Prefix(ValueScreen __instance, int __0)
	{
		return CartValueDisplay.TryHandleValueScreenUpdate(__instance, __0);
	}
}