Decompiled source of UltimateLethal StorePriceModifier v0.1.0

BepInEx/plugins/UltimateLethal_StorePriceModifier/UltimateLethal.StorePriceModifier.dll

Decompiled 8 hours ago
using System;
using System.Collections;
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.Configuration;
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("UltimateLethal.StorePriceModifier")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("UltimateLethal.StorePriceModifier")]
[assembly: AssemblyTitle("UltimateLethal.StorePriceModifier")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace UltimateLethal.StorePriceModifier
{
	[BepInPlugin("com.ultimatelethal.storepricemodifier", "Ultimate Lethal Store Price Modifier", "0.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class PriceRule
		{
			internal string DisplayName;

			internal int Price;

			internal bool Matched;
		}

		public const string Guid = "com.ultimatelethal.storepricemodifier";

		public const string Name = "Ultimate Lethal Store Price Modifier";

		public const string Version = "0.1.0";

		private const string DefaultOverrides = "Belt Bag=800;Company Hauler=250;Assault Rifle=450;Bolt-Action Rifle=500;Flame Thrower=400;Heavy Shotgun=375;Pistol=300;Revolver=275";

		internal static Plugin Instance;

		internal static ConfigEntry<string> PriceOverrides;

		internal static ConfigEntry<bool> LogSuccessfulOverrides;

		private Harmony _harmony;

		private float _nextApplyAt;

		private string _lastRawOverrides;

		private readonly Dictionary<string, PriceRule> _rules = new Dictionary<string, PriceRule>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<string> _loggedMatches = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<string> _loggedMissing = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private float _startupTime;

		private void Awake()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			Instance = this;
			PriceOverrides = ((BaseUnityPlugin)this).Config.Bind<string>("Store Prices", "Overrides", "Belt Bag=800;Company Hauler=250;Assault Rifle=450;Bolt-Action Rifle=500;Flame Thrower=400;Heavy Shotgun=375;Pistol=300;Revolver=275", "Semicolon-separated Item Name=Price rules. Names ignore spaces, hyphens and punctuation. Use -1 to leave a listed item unchanged. This is the authoritative Ultimate Lethal store balance list.");
			LogSuccessfulOverrides = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Log Successful Overrides", true, "Log each configured store item the first time Ultimate Lethal successfully applies its price.");
			ParseRules(force: true);
			_startupTime = Time.unscaledTime;
			_harmony = new Harmony("com.ultimatelethal.storepricemodifier");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded with {2} configured price override(s).", "Ultimate Lethal Store Price Modifier", "0.1.0", _rules.Count));
			RequestImmediateApply();
		}

		private void OnDestroy()
		{
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			if (Instance == this)
			{
				Instance = null;
			}
		}

		private void Update()
		{
			ParseRules(force: false);
			if (!(Time.unscaledTime < _nextApplyAt))
			{
				float num = Time.unscaledTime - _startupTime;
				_nextApplyAt = Time.unscaledTime + ((num < 20f) ? 0.5f : 3f);
				ApplyAll();
				if (num > 12f)
				{
					LogMissingRulesOnce();
				}
			}
		}

		internal void RequestImmediateApply()
		{
			_nextApplyAt = 0f;
		}

		private void ParseRules(bool force)
		{
			string text = PriceOverrides?.Value ?? string.Empty;
			if (!force && string.Equals(text, _lastRawOverrides, StringComparison.Ordinal))
			{
				return;
			}
			_lastRawOverrides = text;
			_rules.Clear();
			_loggedMatches.Clear();
			_loggedMissing.Clear();
			string[] array = text.Split(new char[3] { ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text2 in array)
			{
				int num = text2.IndexOf('=');
				if (num <= 0 || num >= text2.Length - 1)
				{
					continue;
				}
				string text3 = text2.Substring(0, num).Trim();
				string s = text2.Substring(num + 1).Trim();
				if (!string.IsNullOrWhiteSpace(text3) && int.TryParse(s, out var result) && result >= -1 && result >= 0)
				{
					string text4 = Normalize(text3);
					if (text4.Length != 0)
					{
						_rules[text4] = new PriceRule
						{
							DisplayName = text3,
							Price = result
						};
					}
				}
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Parsed {_rules.Count} Ultimate Lethal store price override(s).");
			RequestImmediateApply();
		}

		internal void ApplyAll()
		{
			if (_rules.Count != 0)
			{
				ApplyBetterArmoryConfigAdapter();
				ApplyItemPrices();
				ApplyVehiclePrices();
			}
		}

		private void ApplyItemPrices()
		{
			Item[] array;
			try
			{
				array = Resources.FindObjectsOfTypeAll<Item>();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Item price scan failed: " + ex.GetBaseException().Message));
				return;
			}
			foreach (Item val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string text = (string.IsNullOrWhiteSpace(val.itemName) ? ((Object)val).name : val.itemName);
				if (!TryGetRule(text, out var rule))
				{
					continue;
				}
				try
				{
					if (val.creditsWorth != rule.Price)
					{
						val.creditsWorth = rule.Price;
					}
					MarkMatched(rule, "Item '" + text + "'");
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to price '" + text + "': " + ex2.GetBaseException().Message));
				}
			}
		}

		private void ApplyBetterArmoryConfigAdapter()
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			if (!Chainloader.PluginInfos.TryGetValue("com.y4ngz.betterarmory", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null)
			{
				return;
			}
			ConfigFile config = value.Instance.Config;
			if (config == null)
			{
				return;
			}
			string[] array = new string[6] { "Assault Rifle", "Bolt-Action Rifle", "Flame Thrower", "Heavy Shotgun", "Pistol", "Revolver" };
			ConfigEntry<int> val2 = default(ConfigEntry<int>);
			foreach (string text in array)
			{
				if (!TryGetRule(text, out var rule))
				{
					continue;
				}
				try
				{
					ConfigDefinition val = new ConfigDefinition("Weapon: " + text, "Shop Price");
					if (config.TryGetEntry<int>(val, ref val2) && val2 != null)
					{
						if (val2.Value != rule.Price)
						{
							val2.Value = rule.Price;
						}
						MarkMatched(rule, "BetterArmory '" + text + "'");
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("BetterArmory adapter failed for '" + text + "': " + ex.GetBaseException().Message));
				}
			}
		}

		private void ApplyVehiclePrices()
		{
			if (!TryGetRule("Company Hauler", out var rule))
			{
				return;
			}
			Terminal val = Object.FindObjectOfType<Terminal>();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			int num = 0;
			FieldInfo[] fields = ((object)val).GetType().GetFields(bindingAttr);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.Name.IndexOf("vehicle", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					try
					{
						num += ApplyVehicleEnumerable(fieldInfo.GetValue(val) as IEnumerable, rule);
					}
					catch
					{
					}
				}
			}
			PropertyInfo[] properties = ((object)val).GetType().GetProperties(bindingAttr);
			foreach (PropertyInfo propertyInfo in properties)
			{
				if (propertyInfo.CanRead && propertyInfo.Name.IndexOf("vehicle", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					try
					{
						num += ApplyVehicleEnumerable(propertyInfo.GetValue(val, null) as IEnumerable, rule);
					}
					catch
					{
					}
				}
			}
			if (num > 0)
			{
				MarkMatched(rule, "Company Hauler vehicle registration");
			}
		}

		private int ApplyVehicleEnumerable(IEnumerable entries, PriceRule rule)
		{
			if (entries == null)
			{
				return 0;
			}
			int num = 0;
			foreach (object entry in entries)
			{
				if (entry != null && ObjectContainsName(entry, "hauler") && TrySetIntPriceMember(entry, rule.Price))
				{
					num++;
				}
			}
			return num;
		}

		private static bool ObjectContainsName(object entry, string token)
		{
			Type type = entry.GetType();
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			string[] array = new string[5] { "vehicleDisplayName", "vehicleName", "displayName", "name", "itemName" };
			foreach (string name in array)
			{
				try
				{
					if (type.GetField(name, bindingAttr)?.GetValue(entry) is string text && text.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
					PropertyInfo property = type.GetProperty(name, bindingAttr);
					if (property != null && property.CanRead && property.GetValue(entry, null) is string text2 && text2.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
				}
				catch
				{
				}
			}
			Object val = (Object)((entry is Object) ? entry : null);
			if (val != null && !string.IsNullOrEmpty(val.name))
			{
				return val.name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private static bool TrySetIntPriceMember(object entry, int price)
		{
			Type type = entry.GetType();
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			string[] array = new string[5] { "creditsWorth", "creditsCost", "creditCost", "price", "cost" };
			foreach (string name in array)
			{
				try
				{
					FieldInfo field = type.GetField(name, bindingAttr);
					if (field != null && field.FieldType == typeof(int))
					{
						field.SetValue(entry, price);
						return true;
					}
					PropertyInfo property = type.GetProperty(name, bindingAttr);
					if (property != null && property.CanWrite && property.PropertyType == typeof(int))
					{
						property.SetValue(entry, price, null);
						return true;
					}
				}
				catch
				{
				}
			}
			return false;
		}

		private bool TryGetRule(string name, out PriceRule rule)
		{
			rule = null;
			string text = Normalize(name);
			if (text.Length > 0)
			{
				return _rules.TryGetValue(text, out rule);
			}
			return false;
		}

		private void MarkMatched(PriceRule rule, string source)
		{
			if (rule != null)
			{
				rule.Matched = true;
				ConfigEntry<bool> logSuccessfulOverrides = LogSuccessfulOverrides;
				if (logSuccessfulOverrides != null && logSuccessfulOverrides.Value && _loggedMatches.Add(rule.DisplayName))
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"STORE PRICE: {rule.DisplayName} = {rule.Price} credits ({source}).");
				}
			}
		}

		private void LogMissingRulesOnce()
		{
			foreach (PriceRule value in _rules.Values)
			{
				if (!value.Matched && _loggedMissing.Add(value.DisplayName))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)($"STORE PRICE target not found yet: '{value.DisplayName}' ({value.Price} credits). " + "Check the runtime item name if this remains missing after store registration."));
				}
			}
		}

		private static string Normalize(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			char[] array = new char[value.Length];
			int length = 0;
			foreach (char c in value)
			{
				if (char.IsLetterOrDigit(c))
				{
					array[length++] = char.ToLowerInvariant(c);
				}
			}
			return new string(array, 0, length);
		}
	}
	[HarmonyPatch]
	internal static class TerminalStoreRefreshHooks
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			string[] array = new string[5] { "Start", "TextPostProcess", "LoadNewNode", "LoadNewNodeIfAffordable", "ParsePlayerSentence" };
			HashSet<MethodBase> seen = new HashSet<MethodBase>();
			string[] array2 = array;
			foreach (string name in array2)
			{
				MethodInfo[] methods = typeof(Terminal).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				MethodInfo[] array3 = methods;
				foreach (MethodInfo methodInfo in array3)
				{
					if (methodInfo.Name == name && seen.Add(methodInfo))
					{
						yield return methodInfo;
					}
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			Plugin.Instance?.ApplyAll();
			Plugin.Instance?.RequestImmediateApply();
		}
	}
}