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 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.1.0")] public class Plugin : BaseUnityPlugin { internal struct TemplateInfo { public GameObject prefab; public string resourceId; public float value; } public const string Guid = "themorningstar.loottopup"; internal static ManualLogSource Log = null; internal static ConfigEntry TargetMultiplier; internal static ConfigEntry MinValuables; internal static ConfigEntry WatchdogEnabled; internal static ConfigEntry WatchdogInterval; internal static ConfigEntry RespectHuntingSeason; internal static ConfigEntry SkipMutatorNames; internal static ConfigEntry MaxSpawnsPerLevel; internal static bool ToppedUpThisLevel; internal static int SpawnedThisLevel; internal static readonly List Templates = new List(); internal static bool Busy; 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("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("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("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("Watchdog", "IntervalSeconds", 4f, "How often the watchdog re-checks completability during a level."); RespectHuntingSeason = ((BaseUnityPlugin)this).Config.Bind("Mutators", "RespectNoLootMutators", true, "Skip all topup when a mutator that intentionally removes loot (e.g. Hunting Season) is active."); SkipMutatorNames = ((BaseUnityPlugin)this).Config.Bind("Mutators", "SkipMutatorNames", "Hunting Season", "Comma-separated mutator display names during which topup is disabled."); MaxSpawnsPerLevel = ((BaseUnityPlugin)this).Config.Bind("Topup", "MaxSpawnsPerLevel", 60, "Hard cap on how many valuables this mod will spawn in a single level (runaway guard)."); 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.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(); 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(new Plugin.TemplateInfo { prefab = gameObject, resourceId = "Valuables/" + ((Object)gameObject).name.Replace("(Clone)", "").Trim(), value = val.dollarValueCurrent }); } } } } public static void Evaluate(RoundDirector rd, string reason) { //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0301: 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 list = (from v in Object.FindObjectsOfType() 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 source = ((Plugin.Templates.Count > 0) ? Plugin.Templates : list.Select((ValuableObject v) => new Plugin.TemplateInfo { prefab = ((Component)v).gameObject, resourceId = "Valuables/" + ((Object)((Component)v).gameObject).name.Replace("(Clone)", "").Trim(), value = 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 list2 = source.Where((Plugin.TemplateInfo t) => t.value <= avgVal).ToList(); if (list2.Count == 0) { list2 = source; } bool flag2 = SemiFunc.IsMultiplayer(); Plugin.Busy = true; int num9 = 0; int num10 = 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) && num10 < 500 && Plugin.SpawnedThisLevel + num9 < Plugin.MaxSpawnsPerLevel.Value) { num10++; List list3 = ((num6 < num7) ? source : list2); Plugin.TemplateInfo templateInfo = list3[Random.Range(0, list3.Count)]; if ((Object)(object)templateInfo.prefab == (Object)null) { continue; } Vector3 val = templateInfo.prefab.transform.position + new Vector3(Random.Range(-1.2f, 1.2f), 0.7f, Random.Range(-1.2f, 1.2f)); Quaternion rotation = Random.rotation; GameObject val2; try { val2 = (flag2 ? PhotonNetwork.InstantiateRoomObject(templateInfo.resourceId, val, rotation, (byte)0, (object[])null) : Object.Instantiate(templateInfo.prefab, val, rotation)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("LootTopup: spawn failed (" + ex.Message + "); trying another.")); continue; } if ((Object)(object)val2 == (Object)null) { continue; } ValuableObject component = val2.GetComponent(); if (!((Object)(object)component == (Object)null)) { try { component.DollarValueSetLogic(); } catch { } num6 += component.dollarValueCurrent; num9++; } } } finally { Plugin.Busy = false; } Plugin.SpawnedThisLevel += num9; Plugin.Log.LogInfo((object)$"LootTopup ({reason}): spawned {num9} valuable(s); 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(); } }).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) { } } }