Decompiled source of Loot Topup v1.2.0

LootTopup.dll

Decompiled an hour ago
using System;
using System.Collections;
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 Photon.Pun;
using REPOLib.Modules;
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("LootTopup")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("LootTopup")]
[assembly: AssemblyTitle("LootTopup")]
[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 LootTopup
{
	[BepInPlugin("themorningstar.loottopup", "Loot Topup", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal struct TemplateInfo
		{
			public GameObject prefab;

			public string resourceId;

			public PrefabRef repoLibRef;

			public float value;
		}

		public const string Guid = "themorningstar.loottopup";

		internal static ManualLogSource Log = null;

		internal static ConfigEntry<float> TargetMultiplier;

		internal static ConfigEntry<int> MinValuables;

		internal static ConfigEntry<bool> WatchdogEnabled;

		internal static ConfigEntry<float> WatchdogInterval;

		internal static ConfigEntry<bool> RespectHuntingSeason;

		internal static ConfigEntry<string> SkipMutatorNames;

		internal static ConfigEntry<int> MaxSpawnsPerLevel;

		internal static bool ToppedUpThisLevel;

		internal static int SpawnedThisLevel;

		internal static readonly List<TemplateInfo> Templates = new List<TemplateInfo>();

		internal static bool Busy;

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

		private void Awake()
		{
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			TargetMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Topup", "TargetMultiplier", 1.3f, "Top up until achievable value is at least haulGoal * this. 1.0 = exactly completable; 1.3 = 30% buffer.");
			MinValuables = ((BaseUnityPlugin)this).Config.Bind<int>("Topup", "MinValuables", 8, "Guarantee at least this many valuables on the map at level start (anti-fragility for sparse maps). 0 disables.");
			WatchdogEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Watchdog", "Enabled", true, "Detect mid-level softlocks (too much value destroyed to finish) and top up so the run stays winnable.");
			WatchdogInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Watchdog", "IntervalSeconds", 4f, "How often the watchdog re-checks completability during a level.");
			RespectHuntingSeason = ((BaseUnityPlugin)this).Config.Bind<bool>("Mutators", "RespectNoLootMutators", true, "Skip all topup when a mutator that intentionally removes loot (e.g. Hunting Season) is active.");
			SkipMutatorNames = ((BaseUnityPlugin)this).Config.Bind<string>("Mutators", "SkipMutatorNames", "Hunting Season", "Comma-separated mutator display names during which topup is disabled.");
			MaxSpawnsPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Topup", "MaxSpawnsPerLevel", 60, "Hard cap on how many valuables this mod will spawn in a single level (runaway guard). Failed spawns also count toward this cap.");
			try
			{
				new Harmony("themorningstar.loottopup").PatchAll();
				Log.LogInfo((object)"LootTopup: armed (min-count floor + mid-level softlock watchdog; respects no-loot mutators).");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("LootTopup: patch failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(LevelGenerator), "Start")]
	internal static class ResetGuardPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			Plugin.ToppedUpThisLevel = false;
			Plugin.SpawnedThisLevel = 0;
			Plugin.Templates.Clear();
			Plugin.SpawnBlacklist.Clear();
			Plugin.Busy = false;
		}
	}
	[HarmonyPatch(typeof(RoundDirector), "StartRoundLogic")]
	internal static class StartPatch
	{
		[HarmonyPostfix]
		private static void Postfix(RoundDirector __instance)
		{
			if (!Plugin.ToppedUpThisLevel && SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.RunIsLevel())
			{
				Plugin.ToppedUpThisLevel = true;
				((MonoBehaviour)__instance).StartCoroutine(Driver(__instance));
			}
		}

		private static IEnumerator Driver(RoundDirector rd)
		{
			yield return (object)new WaitForSeconds(2.5f);
			TopupLogic.CaptureTemplates();
			TopupLogic.Evaluate(rd, "level start");
			if (!Plugin.WatchdogEnabled.Value)
			{
				yield break;
			}
			WaitForSeconds wait = new WaitForSeconds(Mathf.Max(1f, Plugin.WatchdogInterval.Value));
			while ((Object)(object)rd != (Object)null && SemiFunc.RunIsLevel())
			{
				yield return wait;
				if ((Object)(object)rd == (Object)null || !SemiFunc.RunIsLevel())
				{
					break;
				}
				TopupLogic.Evaluate(rd, "watchdog");
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "BreakRPC")]
	internal static class BreakTriggerPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			if (Plugin.WatchdogEnabled.Value && SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.RunIsLevel())
			{
				RoundDirector instance = RoundDirector.instance;
				if ((Object)(object)instance != (Object)null)
				{
					TopupLogic.Evaluate(instance, "item damaged");
				}
			}
		}
	}
	internal static class TopupLogic
	{
		private static Type _mutatorManagerType;

		private static bool _mutatorLookupDone;

		public static void CaptureTemplates()
		{
			Plugin.Templates.Clear();
			ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>();
			foreach (ValuableObject val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					GameObject gameObject = ((Component)val).gameObject;
					if (!((Object)(object)gameObject == (Object)null))
					{
						Plugin.Templates.Add(MakeTemplate(gameObject, val.dollarValueCurrent));
					}
				}
			}
		}

		private static Plugin.TemplateInfo MakeTemplate(GameObject go, float value)
		{
			string text = "Valuables/" + ((Object)go).name.Replace("(Clone)", "").Trim();
			PrefabRef repoLibRef = default(PrefabRef);
			NetworkPrefabs.TryGetNetworkPrefabRef(text, ref repoLibRef);
			return new Plugin.TemplateInfo
			{
				prefab = go,
				resourceId = text,
				repoLibRef = repoLibRef,
				value = value
			};
		}

		private static GameObject TrySpawn(Plugin.TemplateInfo tmpl, bool mp, Vector3 pos, Quaternion rot)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: 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)
			//IL_0030: 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_0020: Unknown result type (might be due to invalid IL or missing references)
			if (!mp)
			{
				return Object.Instantiate<GameObject>(tmpl.prefab, pos, rot);
			}
			if (tmpl.repoLibRef != null)
			{
				return NetworkPrefabs.SpawnNetworkPrefab(tmpl.repoLibRef, pos, rot, (byte)0, (object[])null);
			}
			return PhotonNetwork.InstantiateRoomObject(tmpl.resourceId, pos, rot, (byte)0, (object[])null);
		}

		public static void Evaluate(RoundDirector rd, string reason)
		{
			//IL_030d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0335: Unknown result type (might be due to invalid IL or missing references)
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			//IL_0346: Unknown result type (might be due to invalid IL or missing references)
			//IL_034c: Unknown result type (might be due to invalid IL or missing references)
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Busy || (Object)(object)rd == (Object)null)
			{
				return;
			}
			int haulGoal = rd.haulGoal;
			if (haulGoal <= 0 || Plugin.SpawnedThisLevel >= Plugin.MaxSpawnsPerLevel.Value || (Plugin.RespectHuntingSeason.Value && NoLootMutatorActive()))
			{
				return;
			}
			List<ValuableObject> list = (from v in Object.FindObjectsOfType<ValuableObject>()
				where (Object)(object)v != (Object)null
				select v).ToList();
			int count = list.Count;
			float num = 0f;
			for (int num2 = 0; num2 < list.Count; num2++)
			{
				num += list[num2].dollarValueCurrent;
			}
			int num3 = Mathf.Max(1, rd.extractionPoints);
			float num4 = (float)haulGoal / (float)num3;
			float num5 = (float)rd.extractionPointsCompleted * num4;
			float num6 = num + num5;
			float num7 = (float)haulGoal * Plugin.TargetMultiplier.Value;
			int value = Plugin.MinValuables.Value;
			bool num8 = num6 < num7;
			bool flag = count < value;
			if (!num8 && !flag)
			{
				return;
			}
			List<Plugin.TemplateInfo> source = ((Plugin.Templates.Count > 0) ? Plugin.Templates : list.Select((ValuableObject v) => MakeTemplate(((Component)v).gameObject, v.dollarValueCurrent)).ToList());
			source = source.Where((Plugin.TemplateInfo t) => (Object)(object)t.prefab != (Object)null).ToList();
			if (source.Count == 0)
			{
				Plugin.Log.LogWarning((object)$"LootTopup ({reason}): level short (goal={haulGoal}, achievable={num6:0}, items={count}) but no templates to spawn from.");
				return;
			}
			float avgVal = source.Average((Plugin.TemplateInfo t) => t.value);
			List<Plugin.TemplateInfo> list2 = source.Where((Plugin.TemplateInfo t) => t.value <= avgVal).ToList();
			if (list2.Count == 0)
			{
				list2 = source;
			}
			bool mp = SemiFunc.IsMultiplayer();
			Plugin.Busy = true;
			int num9 = 0;
			int num10 = 0;
			int num11 = 0;
			Plugin.Log.LogInfo((object)$"LootTopup ({reason}): topping up. goal={haulGoal} achievable={num6:0} (target {num7:0}), items={count} (min {value}).");
			try
			{
				while ((num6 < num7 || count + num9 < value) && num11 < 500 && Plugin.SpawnedThisLevel + num10 < Plugin.MaxSpawnsPerLevel.Value)
				{
					num11++;
					List<Plugin.TemplateInfo> list3 = ((num6 < num7) ? source : list2).Where((Plugin.TemplateInfo x) => !Plugin.SpawnBlacklist.Contains(x.resourceId)).ToList();
					if (list3.Count == 0)
					{
						Plugin.Log.LogWarning((object)("LootTopup (" + reason + "): all viable templates are blacklisted; stopping topup."));
						break;
					}
					Plugin.TemplateInfo tmpl = list3[Random.Range(0, list3.Count)];
					if ((Object)(object)tmpl.prefab == (Object)null)
					{
						continue;
					}
					Vector3 pos = tmpl.prefab.transform.position + new Vector3(Random.Range(-1.2f, 1.2f), 0.7f, Random.Range(-1.2f, 1.2f));
					Quaternion rotation = Random.rotation;
					GameObject val;
					try
					{
						val = TrySpawn(tmpl, mp, pos, rotation);
					}
					catch (Exception ex)
					{
						if (Plugin.SpawnBlacklist.Add(tmpl.resourceId))
						{
							Plugin.Log.LogWarning((object)("LootTopup: spawn exception for '" + tmpl.resourceId + "' (" + ex.Message + "); blacklisting for this level."));
						}
						num10++;
						continue;
					}
					if ((Object)(object)val == (Object)null)
					{
						if (Plugin.SpawnBlacklist.Add(tmpl.resourceId))
						{
							Plugin.Log.LogWarning((object)("LootTopup: spawn returned null for '" + tmpl.resourceId + "'; blacklisting for this level."));
						}
						num10++;
						continue;
					}
					ValuableObject component = val.GetComponent<ValuableObject>();
					if (!((Object)(object)component == (Object)null))
					{
						try
						{
							component.DollarValueSetLogic();
						}
						catch
						{
						}
						num6 += component.dollarValueCurrent;
						num9++;
						num10++;
					}
				}
			}
			finally
			{
				Plugin.Busy = false;
			}
			Plugin.SpawnedThisLevel += num10;
			int num12 = num10 - num9;
			string text = ((num12 > 0) ? $" ({num12} failed/blacklisted)" : "");
			Plugin.Log.LogInfo((object)$"LootTopup ({reason}): spawned {num9} valuable(s){text}; achievable now ~{num6:0} vs goal {haulGoal}, level total spawned {Plugin.SpawnedThisLevel}.");
		}

		private static bool NoLootMutatorActive()
		{
			try
			{
				string[] array = (from s in (Plugin.SkipMutatorNames.Value ?? "").Split(',')
					select s.Trim() into s
					where s.Length > 0
					select s).ToArray();
				if (array.Length == 0)
				{
					return false;
				}
				if (!_mutatorLookupDone)
				{
					_mutatorLookupDone = true;
					_mutatorManagerType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(delegate(Assembly a)
					{
						try
						{
							return a.GetTypes();
						}
						catch
						{
							return Array.Empty<Type>();
						}
					}).FirstOrDefault((Type t) => t.Name == "MutatorManager");
				}
				if (_mutatorManagerType == null)
				{
					return false;
				}
				object obj = (_mutatorManagerType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public) ?? _mutatorManagerType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public))?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				if (!(obj.GetType().GetProperty("RegisteredMutators")?.GetValue(obj) is IEnumerable enumerable))
				{
					return false;
				}
				foreach (object item in enumerable)
				{
					object obj2 = item.GetType().GetProperty("Value")?.GetValue(item);
					if (obj2 == null)
					{
						continue;
					}
					PropertyInfo property = obj2.GetType().GetProperty("Active");
					if (property == null)
					{
						continue;
					}
					object value = property.GetValue(obj2);
					if (value is bool && (bool)value)
					{
						string mname = (obj2.GetType().GetProperty("MutatorName")?.GetValue(obj2) as string) ?? "";
						if (array.Any((string s) => string.Equals(s, mname, StringComparison.OrdinalIgnoreCase)))
						{
							Plugin.Log.LogInfo((object)("LootTopup: '" + mname + "' mutator active - skipping topup (intentional no-loot)."));
							return true;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("LootTopup: mutator check failed (proceeding): " + ex.Message));
			}
			return false;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		internal IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}