Decompiled source of ValuableRebalance v3.1.0

ValuableRebalance.dll

Decompiled 9 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 System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using REPOLib.Modules;
using REPOLib.Objects.Sdk;
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: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("ValuableRebalance")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ValuableRebalance")]
[assembly: AssemblyTitle("ValuableRebalance")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 ValuableRebalance
{
	[BepInPlugin("themorningstar.valuablerebalance", "Valuable Balance", "3.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal sealed class RefComparer : IEqualityComparer<PrefabRef>
		{
			public bool Equals(PrefabRef a, PrefabRef b)
			{
				return a == b;
			}

			public int GetHashCode(PrefabRef o)
			{
				return RuntimeHelpers.GetHashCode(o);
			}
		}

		public const string Guid = "themorningstar.valuablerebalance";

		internal static ManualLogSource Log = null;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> ModdedChancePerSlot;

		internal static ConfigEntry<float> RethemeChance;

		internal static ConfigEntry<bool> Verbose;

		internal static readonly Dictionary<PrefabRef, string> PrefabToBundle = new Dictionary<PrefabRef, string>(new RefComparer());

		internal static readonly HashSet<string> SprinkleCats = new HashSet<string>();

		internal static readonly Dictionary<string, List<PrefabRef>[]> SprinkleBySize = new Dictionary<string, List<PrefabRef>[]>();

		internal static readonly Dictionary<LevelValuables, List<PrefabRef>[]> PresetNativeBySize = new Dictionary<LevelValuables, List<PrefabRef>[]>();

		internal static bool PoolsBuilt;

		internal static object CurrentLevel;

		internal static List<PrefabRef>[] NativeNow = NewPools();

		internal static bool RethemeActive;

		internal static string RethemeCategory;

		internal static List<PrefabRef>[] NewPools()
		{
			List<PrefabRef>[] array = new List<PrefabRef>[7];
			for (int i = 0; i < 7; i++)
			{
				array[i] = new List<PrefabRef>();
			}
			return array;
		}

		private void Awake()
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Expected O, but got Unknown
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Expected O, but got Unknown
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Balance", "Enabled", true, "Master switch.");
			ModdedChancePerSlot = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "ModdedChancePerSlot", 0.06f, "Chance (0-1) each valuable spawn is a global modded item instead of the map's native loot. ~0.06 => roughly 1-2 modded items per map.");
			RethemeChance = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "RethemeChance", 0.02f, "Chance (0-1) an entire map is re-themed so every valuable comes from a single global pack (all-Pokemon map, etc.).");
			Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Balance", "Verbose", false, "Log pool sizes, sprinkle categories, and per-map re-theme rolls.");
			try
			{
				Harmony val = new Harmony("themorningstar.valuablerebalance");
				MethodInfo methodInfo = AccessTools.Method(typeof(Valuables), "RegisterValuable", new Type[1] { typeof(ValuableContent) }, (Type[])null);
				if (methodInfo != null)
				{
					val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Track).GetMethod("Post")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Valuables), "RegisterInitialValuables", (Type[])null, (Type[])null);
				if (methodInfo2 != null)
				{
					val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(Pools).GetMethod("OnDrain")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				else
				{
					Log.LogWarning((object)"ValuableBalance: RegisterInitialValuables not found.");
				}
				MethodInfo methodInfo3 = AccessTools.Method(typeof(LevelGenerator), "Start", (Type[])null, (Type[])null);
				if (methodInfo3 != null)
				{
					val.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(Pools).GetMethod("OnLevelStart")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				MethodInfo methodInfo4 = AccessTools.Method(typeof(ValuableDirector), "SpawnValuable", (Type[])null, (Type[])null);
				if (methodInfo4 != null)
				{
					val.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(Pools).GetMethod("OnSpawn")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				else
				{
					Log.LogWarning((object)"ValuableBalance: ValuableDirector.SpawnValuable not found; per-slot balancing disabled.");
				}
				Log.LogInfo((object)"ValuableBalance: armed (level-aware native pools + global sprinkle + rare re-theme).");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("ValuableBalance: setup failed: " + ex));
			}
		}
	}
	internal static class Track
	{
		public static void Post(ValuableContent __0, PrefabRef __result)
		{
			try
			{
				if (__result != null && !((Object)(object)__0 == (Object)null) && !((Object)(object)((Content)__0).Bundle == (Object)null))
				{
					string value = ((Object)((Content)__0).Bundle).name?.ToLowerInvariant();
					if (!string.IsNullOrEmpty(value))
					{
						Plugin.PrefabToBundle[__result] = value;
					}
				}
			}
			catch
			{
			}
		}
	}
	internal static class Pools
	{
		private static bool _healthDone;

		private static string Category(PrefabRef pr, Dictionary<string, string> index)
		{
			try
			{
				AssetBundle bundle = ((PrefabRef<GameObject>)(object)pr).Bundle;
				if ((Object)(object)bundle != (Object)null)
				{
					string text = ((Object)bundle).name?.ToLowerInvariant();
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			if (Plugin.PrefabToBundle.TryGetValue(pr, out var value))
			{
				return value;
			}
			return SourceFromBundleIndex(pr, index);
		}

		public static void OnDrain()
		{
			if (!Plugin.Enabled.Value)
			{
				return;
			}
			try
			{
				Plugin.SprinkleCats.Clear();
				Plugin.SprinkleBySize.Clear();
				Plugin.PresetNativeBySize.Clear();
				Dictionary<string, string> index = BuildAssetBundleIndex();
				List<LevelValuables> list = (from lv in Resources.FindObjectsOfTypeAll<LevelValuables>()
					where (Object)(object)lv != (Object)null
					select lv).ToList();
				foreach (LevelValuables item in list)
				{
					if (((Object)item).name == null || ((Object)item).name.IndexOf("Generic", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					List<PrefabRef>[] array = Lists(item);
					foreach (List<PrefabRef> list2 in array)
					{
						if (list2 == null)
						{
							continue;
						}
						foreach (PrefabRef item2 in list2)
						{
							if (item2 != null)
							{
								string text = Category(item2, index);
								if (!string.IsNullOrEmpty(text))
								{
									Plugin.SprinkleCats.Add(text);
								}
							}
						}
					}
				}
				if (Plugin.SprinkleCats.Count == 0)
				{
					foreach (LevelValuables item3 in list)
					{
						List<PrefabRef>[] array = Lists(item3);
						foreach (List<PrefabRef> list3 in array)
						{
							if (list3 == null)
							{
								continue;
							}
							foreach (PrefabRef item4 in list3)
							{
								if (item4 != null)
								{
									string text2 = Category(item4, index);
									if (!string.IsNullOrEmpty(text2))
									{
										Plugin.SprinkleCats.Add(text2);
									}
								}
							}
						}
					}
					if (Plugin.SprinkleCats.Count > 0)
					{
						Plugin.Log.LogWarning((object)"ValuableBalance: no 'Generic' preset found; treating all mod categories as global (theme-scoping disabled).");
					}
				}
				HashSet<PrefabRef> hashSet = new HashSet<PrefabRef>(new Plugin.RefComparer());
				foreach (LevelValuables item5 in list)
				{
					List<PrefabRef>[] array2 = Plugin.NewPools();
					Plugin.PresetNativeBySize[item5] = array2;
					List<PrefabRef>[] array3 = Lists(item5);
					for (int num2 = 0; num2 < 7; num2++)
					{
						if (array3[num2] == null)
						{
							continue;
						}
						foreach (PrefabRef item6 in array3[num2])
						{
							if (item6 == null)
							{
								continue;
							}
							string text3 = Category(item6, index);
							if (!string.IsNullOrEmpty(text3) && Plugin.SprinkleCats.Contains(text3))
							{
								if (hashSet.Add(item6))
								{
									if (!Plugin.SprinkleBySize.TryGetValue(text3, out var value))
									{
										value = Plugin.NewPools();
										Plugin.SprinkleBySize[text3] = value;
									}
									value[num2].Add(item6);
								}
							}
							else
							{
								array2[num2].Add(item6);
							}
						}
					}
				}
				Plugin.PoolsBuilt = true;
				int num3 = Plugin.SprinkleBySize.Values.Sum((List<PrefabRef>[] p) => p.Sum((List<PrefabRef> l) => l.Count));
				Plugin.Log.LogInfo((object)$"ValuableBalance: pools built - {Plugin.SprinkleCats.Count} global sprinkle pack(s) ({num3} items), {Plugin.PresetNativeBySize.Count} map preset(s) kept native.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("ValuableBalance: pool build failed: " + ex));
			}
		}

		public static void OnLevelStart()
		{
			Plugin.RethemeActive = false;
			Plugin.RethemeCategory = null;
			Plugin.CurrentLevel = null;
			if (!Plugin.Enabled.Value || !Plugin.PoolsBuilt)
			{
				return;
			}
			if (!_healthDone)
			{
				_healthDone = true;
				try
				{
					HealthCheck();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("ValuableBalance: health check failed: " + ex.Message));
				}
			}
			try
			{
				if (Plugin.SprinkleCats.Count > 0 && Random.value < Plugin.RethemeChance.Value)
				{
					List<string> list = Plugin.SprinkleCats.ToList();
					Plugin.RethemeCategory = list[Random.Range(0, list.Count)];
					Plugin.RethemeActive = true;
					Plugin.Log.LogInfo((object)("ValuableBalance: THEMED MAP - all valuables from '" + Plugin.RethemeCategory + "'."));
				}
			}
			catch
			{
			}
		}

		private static void EnsureNative(Level lvl)
		{
			if (lvl == Plugin.CurrentLevel)
			{
				return;
			}
			Plugin.CurrentLevel = lvl;
			List<PrefabRef>[] array = Plugin.NewPools();
			try
			{
				if ((Object)(object)lvl != (Object)null && lvl.ValuablePresets != null)
				{
					HashSet<PrefabRef> hashSet = new HashSet<PrefabRef>(new Plugin.RefComparer());
					foreach (LevelValuables valuablePreset in lvl.ValuablePresets)
					{
						if (!((Object)(object)valuablePreset != (Object)null) || !Plugin.PresetNativeBySize.TryGetValue(valuablePreset, out var value))
						{
							continue;
						}
						for (int i = 0; i < 7; i++)
						{
							foreach (PrefabRef item in value[i])
							{
								if (item != null && hashSet.Add(item))
								{
									array[i].Add(item);
								}
							}
						}
					}
				}
			}
			catch
			{
			}
			Plugin.NativeNow = array;
		}

		public static void OnSpawn(ref PrefabRef _valuable, ValuableVolume _volume, string _path)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected I4, but got Unknown
			try
			{
				if (!Plugin.Enabled.Value || !Plugin.PoolsBuilt || (Object)(object)_volume == (Object)null)
				{
					return;
				}
				int size = (int)_volume.VolumeType;
				if (size < 0 || size > 6)
				{
					return;
				}
				Level lvl = null;
				try
				{
					lvl = (((Object)(object)LevelGenerator.Instance != (Object)null) ? LevelGenerator.Instance.Level : null);
				}
				catch
				{
				}
				EnsureNative(lvl);
				PrefabRef val = null;
				if (Plugin.RethemeActive && Plugin.RethemeCategory != null && Plugin.SprinkleBySize.TryGetValue(Plugin.RethemeCategory, out var value))
				{
					val = RandomFrom(value[size]) ?? RandomFrom(Plugin.NativeNow[size]);
				}
				else if (Random.value < Plugin.ModdedChancePerSlot.Value && Plugin.SprinkleCats.Count > 0)
				{
					List<PrefabRef>[] value2;
					List<string> list = Plugin.SprinkleCats.Where((string c) => Plugin.SprinkleBySize.TryGetValue(c, out value2) && value2[size].Count > 0).ToList();
					val = ((list.Count <= 0) ? RandomFrom(Plugin.NativeNow[size]) : RandomFrom(Plugin.SprinkleBySize[list[Random.Range(0, list.Count)]][size]));
				}
				else
				{
					val = RandomFrom(Plugin.NativeNow[size]);
				}
				if (val != null && val != _valuable)
				{
					GameObject val2 = null;
					try
					{
						val2 = ((PrefabRef<GameObject>)(object)val).Prefab;
					}
					catch
					{
					}
					if ((Object)(object)val2 != (Object)null)
					{
						_valuable = val;
					}
				}
			}
			catch
			{
			}
		}

		private static void HealthCheck()
		{
			int num = Plugin.PresetNativeBySize.Values.Sum((List<PrefabRef>[] p) => p.Sum((List<PrefabRef> l) => l.Count));
			Plugin.Log.LogInfo((object)$"===== ValuableBalance HEALTH CHECK: {Plugin.SprinkleCats.Count} global loot pack(s), {Plugin.PresetNativeBySize.Count} map preset(s), {num} native items =====");
			foreach (string item in Plugin.SprinkleCats.OrderBy((string x) => x))
			{
				List<PrefabRef>[] value;
				int num2 = (Plugin.SprinkleBySize.TryGetValue(item, out value) ? value.Sum((List<PrefabRef> l) => l.Count) : 0);
				Plugin.Log.LogInfo((object)$"  [global pack] {item}: {num2} valuable(s)");
			}
			Plugin.Log.LogInfo((object)"  (If a global loot mod you installed is NOT listed above, it registered nothing = broken.)");
			HashSet<PrefabRef> seen = new HashSet<PrefabRef>(new Plugin.RefComparer());
			int checkedCount = 0;
			int unresolved = 0;
			List<string> broken = new List<string>();
			foreach (KeyValuePair<string, List<PrefabRef>[]> item2 in Plugin.SprinkleBySize)
			{
				Scan(item2.Value, item2.Key);
			}
			foreach (KeyValuePair<LevelValuables, List<PrefabRef>[]> item3 in Plugin.PresetNativeBySize)
			{
				Scan(item3.Value, "native:" + ((Object)item3.Key).name);
			}
			if (broken.Count > 0)
			{
				Plugin.Log.LogWarning((object)$"ValuableBalance: {broken.Count} valuable(s) render as a PLACEHOLDER box (missing model/material) - likely broken:");
				{
					foreach (string item4 in broken)
					{
						Plugin.Log.LogWarning((object)("  [broken-render] " + item4));
					}
					return;
				}
			}
			Plugin.Log.LogInfo((object)$"ValuableBalance: no placeholder-render valuables among {checkedCount} checked ({unresolved} unresolved).");
			void Scan(List<PrefabRef>[] pools, string tag)
			{
				for (int i = 0; i < 7; i++)
				{
					foreach (PrefabRef item5 in pools[i])
					{
						if (item5 != null && seen.Add(item5))
						{
							GameObject val = null;
							try
							{
								val = ((PrefabRef<GameObject>)(object)item5).Prefab;
							}
							catch
							{
							}
							if ((Object)(object)val == (Object)null)
							{
								unresolved++;
							}
							else
							{
								checkedCount++;
								if (RendersAsPlaceholder(val))
								{
									broken.Add("'" + ((Object)val).name + "' (" + tag + ")");
								}
							}
						}
					}
				}
			}
		}

		private static bool RendersAsPlaceholder(GameObject go)
		{
			Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
			if (componentsInChildren == null || componentsInChildren.Length == 0)
			{
				return true;
			}
			Renderer[] array = componentsInChildren;
			foreach (Renderer val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Material[] sharedMaterials = val.sharedMaterials;
				foreach (Material val2 in sharedMaterials)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						string text = ((Object)val2).name ?? "";
						if (!text.StartsWith("Default-") && !text.StartsWith("Sprites-Default") && (!((Object)(object)val2.shader != (Object)null) || !(((Object)val2.shader).name == "Hidden/InternalErrorShader")))
						{
							return false;
						}
					}
				}
			}
			return true;
		}

		private static List<PrefabRef>[] Lists(LevelValuables lv)
		{
			return new List<PrefabRef>[7] { lv.tiny, lv.small, lv.medium, lv.big, lv.wide, lv.tall, lv.veryTall };
		}

		private static PrefabRef RandomFrom(List<PrefabRef> list)
		{
			if (list == null || list.Count == 0)
			{
				return null;
			}
			return list[Random.Range(0, list.Count)];
		}

		private static Dictionary<string, string> BuildAssetBundleIndex()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles())
			{
				if ((Object)(object)allLoadedAssetBundle == (Object)null)
				{
					continue;
				}
				string value = ((Object)allLoadedAssetBundle).name?.ToLowerInvariant();
				if (string.IsNullOrEmpty(value))
				{
					continue;
				}
				string[] allAssetNames;
				try
				{
					if (allLoadedAssetBundle.isStreamedSceneAssetBundle)
					{
						continue;
					}
					allAssetNames = allLoadedAssetBundle.GetAllAssetNames();
					goto IL_0061;
				}
				catch
				{
				}
				continue;
				IL_0061:
				string[] array = allAssetNames;
				foreach (string text in array)
				{
					if (text != null && text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
					{
						string text2 = LeafName(text);
						if (text2.Length > 0 && !dictionary.ContainsKey(text2))
						{
							dictionary[text2] = value;
						}
					}
				}
			}
			return dictionary;
		}

		private static string SourceFromBundleIndex(PrefabRef pr, Dictionary<string, string> index)
		{
			try
			{
				string text = LeafName(((PrefabRef<GameObject>)(object)pr).PrefabName);
				if (text.Length > 0 && index.TryGetValue(text, out var value))
				{
					return value;
				}
			}
			catch
			{
			}
			return null;
		}

		private static string LeafName(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return "";
			}
			string text = path;
			int num = text.LastIndexOfAny(new char[2] { '/', '\\' });
			if (num >= 0)
			{
				text = text.Substring(num + 1);
			}
			if (text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
			{
				text = text.Substring(0, text.Length - 7);
			}
			return text.Trim().ToLowerInvariant();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		internal IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}