Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValuableSpawnController v4.0.0
ValuableSpawnController.dll
Decompiled 3 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; 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("REPOJP")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("4.0.0.0")] [assembly: AssemblyInformationalVersion("4.0.0")] [assembly: AssemblyProduct("ValuableSpawnController")] [assembly: AssemblyTitle("ValuableSpawnController")] [assembly: AssemblyVersion("4.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 REPOJP.ValuableSpawnController { internal static class CategoryLimitTranspiler { internal static List<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, MethodInfo prepare) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown List<CodeInstruction> code = instructions.ToList(); int[] array = (from index in Enumerable.Range(0, code.Count) where code[index].opcode == OpCodes.Stfld && code[index].operand is FieldInfo fieldInfo && fieldInfo.FieldType == typeof(int[]) && fieldInfo.Name.StartsWith("<_volumeIndex>", StringComparison.Ordinal) select index).ToArray(); if (array.Length != 1) { throw new InvalidOperationException($"Expected one valuable array initialization; found {array.Length}."); } code.InsertRange(array[0] + 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Call, (object)prepare) }); return code; } } internal static class ChanceRollTranspiler { internal static List<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, MethodInfo randomRange, MethodInfo weightedRoll) { //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); List<int> list2 = new List<int>(); for (int i = 5; i < list.Count; i++) { if (list[i].opcode == OpCodes.Call && object.Equals(list[i].operand, randomRange) && list[i - 1].opcode == OpCodes.Ldelem_I4 && IsLoadLocal(list[i - 2]) && list[i - 3].opcode == OpCodes.Ldfld && list[i - 3].operand is FieldInfo fieldInfo && fieldInfo.FieldType == typeof(int[]) && fieldInfo.Name.StartsWith("<_chance>", StringComparison.Ordinal) && list[i - 4].opcode == OpCodes.Ldarg_0 && list[i - 5].opcode == OpCodes.Ldc_I4_0) { list2.Add(i); } } if (list2.Count != 1) { throw new InvalidOperationException($"Expected exactly one valuable category chance roll; found {list2.Count}."); } int num = list2[0]; CodeInstruction val = list[num]; CodeInstruction val2 = list[num - 2]; CodeInstruction val3 = new CodeInstruction(val2.opcode, val2.operand); val3.labels.AddRange(val.labels); val3.blocks.AddRange(val.blocks); list.RemoveAt(num); list.InsertRange(num, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3] { val3, new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Call, (object)weightedRoll) }); return list; } private static bool IsLoadLocal(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Ldloc) && !(instruction.opcode == OpCodes.Ldloc_S) && !(instruction.opcode == OpCodes.Ldloc_0) && !(instruction.opcode == OpCodes.Ldloc_1) && !(instruction.opcode == OpCodes.Ldloc_2)) { return instruction.opcode == OpCodes.Ldloc_3; } return true; } } internal sealed class ValuableCounts { internal static readonly string[] CategoryNames = new string[7] { "Tiny", "Small", "Medium", "Big", "Wide", "Tall", "VeryTall" }; private readonly SpawnWeights _weights; private readonly int[] _categories = new int[7]; private readonly int[] _groups = new int[3]; private readonly Dictionary<(int Category, string Name), int> _items = new Dictionary<(int, string), int>(); internal int Total { get; private set; } internal int Unknown { get; private set; } internal int ZeroWeight { get; private set; } internal ValuableCounts(SpawnWeights weights) { _weights = weights; } internal int CategoryCount(int category) { return _categories[category]; } internal int GroupCount(SizeGroup group) { return _groups[(int)group]; } internal void Add(int category, string name, int amount = 1) { if (amount <= 0) { return; } Total += amount; if (category >= 0 && category < _categories.Length) { _categories[category] += amount; _groups[(int)_weights.GetGroup(category)] += amount; if (_weights.GetPercent(category) == 0) { ZeroWeight += amount; } } else { Unknown += amount; } (int, string) key = (category, name ?? "(unnamed)"); _items.TryGetValue(key, out var value); _items[key] = value + amount; } internal string Summary() { return $"Total={Total}; Small={_groups[0]}; Medium={_groups[1]}; Large={_groups[2]}; Unknown={Unknown}; ZeroWeight={ZeroWeight}"; } internal string Categories() { return string.Join("; ", CategoryNames.Select((string name, int category) => $"{name}={_categories[category]}")); } internal IEnumerable<string> Items() { foreach (KeyValuePair<(int, string), int> item2 in _items.OrderBy<KeyValuePair<(int, string), int>, int>((KeyValuePair<(int Category, string Name), int> pair) => pair.Key.Category).ThenBy<KeyValuePair<(int, string), int>, string>((KeyValuePair<(int Category, string Name), int> pair) => pair.Key.Name, StringComparer.Ordinal)) { int item = item2.Key.Item1; string arg = ((item >= 0 && item < CategoryNames.Length) ? $"Category={CategoryNames[item]}; Group={_weights.GetGroup(item)}; Weight={_weights.GetPercent(item)}%" : $"Category=Unknown({item}); Group=Unknown; Weight=Unknown"); yield return $"{arg}; Count={item2.Value}; Name={item2.Key.Item2.Replace('\r', ' ').Replace('\n', ' ')}"; } } } internal sealed class GenerationAudit { private readonly HashSet<int> _observedIds = new HashSet<int>(); internal SpawnWeights Weights { get; } internal ValuableCounts Spawned { get; } internal ValuableCounts Observed { get; } internal bool GenerationEntered { get; } internal bool Completed { get; private set; } internal int VanillaCountLimit { get; private set; } internal int PlannedCountLimit { get; private set; } internal float CurrentValue { get; private set; } internal float ValueLimit { get; private set; } internal string StopReason { get { if (!(CurrentValue > ValueLimit)) { if (PlannedCountLimit > 0) { if (Spawned.Total < PlannedCountLimit || !Weights.UsesVanillaWeights) { return "EligibleSlotsOrCandidatesExhausted"; } return "VanillaCountLimit"; } return "NoEligibleSpawns"; } return "ValueBudget"; } } internal GenerationAudit(SpawnWeights weights, bool generationEntered = true) { Weights = weights; GenerationEntered = generationEntered; Spawned = new ValuableCounts(weights); Observed = new ValuableCounts(weights); } internal void PrepareBudget(int vanillaCountLimit, int plannedCountLimit, float valueLimit) { VanillaCountLimit = vanillaCountLimit; PlannedCountLimit = plannedCountLimit; ValueLimit = valueLimit; } internal void RecordValue(float currentValue, float valueLimit) { CurrentValue = currentValue; ValueLimit = valueLimit; } internal string BudgetSummary() { return $"VanillaCountLimit={VanillaCountLimit}; EffectiveCountLimit={PlannedCountLimit}; Spawned={Spawned.Total}; " + $"ValueTotal={CurrentValue * 1000f:0}; ValueBudget={ValueLimit * 1000f:0}; StopReason={StopReason}"; } internal void RecordSpawn(int category, string name, int amount) { if (!Completed) { Spawned.Add(category, name, amount); } } internal void RecordObserved(int instanceId, int category, string name) { if (!Completed && _observedIds.Add(instanceId)) { Observed.Add(category, name); } } internal bool TryComplete() { if (Completed) { return false; } Completed = true; return true; } } internal enum SizeGroup { Small, Medium, Large } internal enum WeightMode { SizeGroups, PerCategory } internal sealed class SpawnWeights { internal const int DefaultPercent = 100; internal const int MaximumPercent = 1000; internal static readonly SpawnWeights Vanilla = new SpawnWeights(100, 100, 100); private readonly SizeGroup[] _groups; private readonly int[] _categoryPercent; internal WeightMode Mode { get; } internal int SmallPercent { get; } internal int MediumPercent { get; } internal int LargePercent { get; } internal bool UsesVanillaWeights { get { for (int i = 0; i < _groups.Length; i++) { if (GetPercent(i) != 100) { return false; } } return true; } } internal SpawnWeights(int smallPercent, int mediumPercent, int largePercent, SizeGroup[]? groups = null, int[]? categoryPercent = null, WeightMode mode = WeightMode.SizeGroups) { SmallPercent = Clamp(smallPercent); MediumPercent = Clamp(mediumPercent); LargePercent = Clamp(largePercent); Mode = mode; _groups = new SizeGroup[7]; _categoryPercent = new int[7]; for (int i = 0; i < _groups.Length; i++) { _categoryPercent[i] = ((categoryPercent != null && i < categoryPercent.Length) ? Clamp(categoryPercent[i]) : 100); SizeGroup sizeGroup = ((groups != null && i < groups.Length) ? groups[i] : DefaultGroup(i)); SizeGroup[] groups2 = _groups; int num = i; bool flag = (uint)sizeGroup <= 2u; groups2[num] = (flag ? sizeGroup : DefaultGroup(i)); } } internal static SizeGroup DefaultGroup(int category) { switch (category) { case 0: case 1: return SizeGroup.Small; case 2: case 3: return SizeGroup.Medium; default: return SizeGroup.Large; } } internal int CategoryLimit(int category, int vanillaLimit, int slots, int candidates, int totalLimit) { if (UsesVanillaWeights || category < 0 || category >= _groups.Length) { return vanillaLimit; } if (GetPercent(category) == 0 || candidates <= 0) { return 0; } return Math.Max(0, Math.Min(slots, totalLimit)); } internal int GenerationLimit(int vanillaLimit, int[] slots, int[] candidates, bool isGameplay) { if (UsesVanillaWeights || !isGameplay || vanillaLimit <= 0) { return vanillaLimit; } if (slots.Length != _groups.Length || candidates.Length != _groups.Length) { throw new ArgumentException("Expected seven category slot and candidate counts."); } long num = 0L; for (int i = 0; i < _groups.Length; i++) { if (GetPercent(i) > 0 && candidates[i] > 0) { num += Math.Max(0, slots[i]); } } return (int)Math.Min(2147483647L, num); } internal SizeGroup GetGroup(int category) { return _groups[category]; } internal int GetPercent(int vanillaCategory) { if (vanillaCategory < 0 || vanillaCategory >= _groups.Length) { return 100; } if (Mode == WeightMode.PerCategory) { return _categoryPercent[vanillaCategory]; } return _groups[vanillaCategory] switch { SizeGroup.Small => SmallPercent, SizeGroup.Medium => MediumPercent, _ => LargePercent, }; } internal int Roll(int minInclusive, int vanillaMaxExclusive, int vanillaCategory, Func<int, int, int> randomRange) { int percent = GetPercent(vanillaCategory); if (percent == 100 || minInclusive != 0) { return randomRange(minInclusive, vanillaMaxExclusive); } if (percent == 0) { return -1; } if (vanillaMaxExclusive <= 0) { return randomRange(minInclusive, vanillaMaxExclusive); } long val = ((long)vanillaMaxExclusive * (long)percent + 50) / 100; int arg = (int)Math.Max(1L, Math.Min(2147483647L, val)); return randomRange(minInclusive, arg); } private static int Clamp(int percent) { return Math.Max(0, Math.Min(1000, percent)); } } internal sealed class GenerationWeights { private readonly ConditionalWeakTable<object, SpawnWeights> _snapshots = new ConditionalWeakTable<object, SpawnWeights>(); private readonly ConditionalWeakTable<object, SpawnWeights>.CreateValueCallback _capture; internal GenerationWeights(Func<SpawnWeights> capture) { _capture = (object _) => capture(); } internal SpawnWeights Get(object coroutine) { return _snapshots.GetValue(coroutine, _capture); } } [BepInPlugin("REPOJP.ValuableSizeWeights", "ValuableSpawnController", "4.0.0")] public sealed class ValuableSpawnControllerPlugin : BaseUnityPlugin { public const string PluginGuid = "REPOJP.ValuableSizeWeights"; public const string PluginName = "ValuableSpawnController"; public const string PluginVersion = "4.0.0"; private static readonly string[] CategoryNames = new string[7] { "Tiny", "Small", "Medium", "Big", "Wide", "Tall", "Very Tall" }; private static readonly ConfigEntry<string>[] _categoryGroups = new ConfigEntry<string>[7]; private static readonly ConfigEntry<int>[] _categoryWeights = new ConfigEntry<int>[7]; private static ConfigEntry<string> _mode = null; private static Harmony? _harmony; private static ConfigEntry<bool> _enabled = null; private static ConfigEntry<int> _small = null; private static ConfigEntry<int> _medium = null; private static ConfigEntry<int> _large = null; internal static ManualLogSource RuntimeLog { get; private set; } = null; private void Awake() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Expected O, but got Unknown RuntimeLog = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable size-based valuable spawn weights. Host only. Changes apply from the next level generation."); _mode = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Weight Mode", "SizeGroups", new ConfigDescription("SizeGroups: use Small/Medium/Large weights and Category Groups assignments. PerCategory: use each of the seven Category Weights directly. Settings are independent; applies next level generation.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "SizeGroups", "PerCategory" }), Array.Empty<object>())); _small = BindWeight("Small Weight Percent", "Weight for categories assigned to Small"); _medium = BindWeight("Medium Weight Percent", "Weight for categories assigned to Medium"); _large = BindWeight("Large Weight Percent", "Weight for categories assigned to Large"); for (int i = 0; i < CategoryNames.Length; i++) { string text = CategoryNames[i]; _categoryGroups[i] = ((BaseUnityPlugin)this).Config.Bind<string>("Category Groups", text + " Group", SpawnWeights.DefaultGroup(i).ToString(), new ConfigDescription("Assign vanilla " + text + " valuables to Small, Medium or Large for spawn weighting. Physical size and valid spawn locations do not change. Host only; applies next level generation.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Small", "Medium", "Large" }), Array.Empty<object>())); } for (int j = 0; j < CategoryNames.Length; j++) { _categoryWeights[j] = ((BaseUnityPlugin)this).Config.Bind<int>("Category Weights", CategoryNames[j] + " Weight Percent", 100, new ConfigDescription("Used only in PerCategory mode. Weight for vanilla " + CategoryNames[j] + " valuables. 100 = vanilla roll range, 200 = twice, 50 = half, 0 = exclude. Not an exact spawn percentage. Host only; applies next level generation. All weights at 100 preserve vanilla count limits; custom weights use available locations while preserving the total value budget. Category Groups assignments are ignored in this mode.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); } _harmony = new Harmony("REPOJP.ValuableSizeWeights"); try { _harmony.PatchAll(typeof(ValuableSpawnControllerPlugin).Assembly); ValuableSpawnPatch.VerifyInstalled("REPOJP.ValuableSizeWeights"); RuntimeLog.LogInfo((object)$"Spawn patch verified. Build={typeof(ValuableSpawnControllerPlugin).Module.ModuleVersionId}"); } catch (Exception arg) { _harmony.UnpatchSelf(); RuntimeLog.LogError((object)$"Could not patch the vanilla valuable size roll. Vanilla spawning is unchanged. {arg}"); } } private ConfigEntry<int> BindWeight(string name, string sizes) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<int>("Spawn Weights", name, 100, new ConfigDescription(sizes + ". 100 = vanilla roll weight, 200 = twice the roll range, 50 = half, 0 = exclude this size. Range: 0-1000. This is a relative selection weight, not an exact spawn percentage. All effective weights at 100 preserve vanilla count limits. Custom weights use valid spawn locations until the vanilla total value budget is reached or locations run out. Used only in SizeGroups mode. All three at 0 disables normal valuable spawns. Host settings apply from the next level generation.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); } internal static SpawnWeights CaptureWeights() { bool flag = SemiFunc.IsMasterClientOrSingleplayer(); if (!_enabled.Value || !flag) { return SpawnWeights.Vanilla; } SizeGroup[] array = new SizeGroup[7]; for (int i = 0; i < array.Length; i++) { array[i] = (Enum.TryParse<SizeGroup>(_categoryGroups[i].Value, out var result) ? result : SpawnWeights.DefaultGroup(i)); } int[] array2 = new int[7]; for (int j = 0; j < array2.Length; j++) { array2[j] = _categoryWeights[j].Value; } WeightMode mode = ((_mode.Value == "PerCategory") ? WeightMode.PerCategory : WeightMode.SizeGroups); return new SpawnWeights(_small.Value, _medium.Value, _large.Value, array, array2, mode); } private void OnDestroy() { } } [HarmonyPatch] internal static class ValuableSpawnPatch { private static readonly Func<int, int, int> VanillaRandomRange = Random.Range; private static readonly ConditionalWeakTable<object, GenerationAudit> Sessions = new ConditionalWeakTable<object, GenerationAudit>(); private static readonly ConditionalWeakTable<ValuableDirector, GenerationAudit> Directors = new ConditionalWeakTable<ValuableDirector, GenerationAudit>(); private static readonly FieldInfo DirectorField = TargetMethod().DeclaringType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Single((FieldInfo field) => field.FieldType == typeof(ValuableDirector)); private static readonly FieldInfo LimitsField = CategoryArray("<_maxAmount>"); private static readonly FieldInfo VolumesField = CategoryArray("<_volumes>"); private static readonly FieldInfo ValuablesField = CategoryArray("<_valuables>"); private static readonly FieldInfo TotalLimitField = AccessTools.Field(typeof(ValuableDirector), "totalMaxAmount"); private static readonly FieldInfo ValueLimitField = AccessTools.Field(typeof(ValuableDirector), "totalMaxValue"); private static readonly ConditionalWeakTable<object, GenerationAudit>.CreateValueCallback CreateSession = delegate(object coroutine) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown GenerationAudit generationAudit = new GenerationAudit(ValuableSpawnControllerPlugin.CaptureWeights()); ValuableDirector key = (ValuableDirector)DirectorField.GetValue(coroutine); Directors.Remove(key); Directors.Add(key, generationAudit); return generationAudit; }; private static FieldInfo CategoryArray(string prefix) { return TargetMethod().DeclaringType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Single((FieldInfo field) => field.Name.StartsWith(prefix, StringComparison.Ordinal)); } private static MethodBase TargetMethod() { return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(ValuableDirector), "SetupHost", (Type[])null, (Type[])null)) ?? throw new MissingMethodException("ValuableDirector.SetupHost.MoveNext"); } internal static void VerifyInstalled(string owner) { Patches patchInfo = Harmony.GetPatchInfo(TargetMethod()); if (patchInfo == null || !patchInfo.Transpilers.Any((Patch patch) => patch.owner == owner) || !patchInfo.Prefixes.Any((Patch patch) => patch.owner == owner)) { throw new InvalidOperationException("The valuable generation patch was not registered."); } MethodInfo[] array = new MethodInfo[2] { AccessTools.Method(typeof(ValuableDirector), "SpawnValuable", (Type[])null, (Type[])null), AccessTools.Method(typeof(LevelGenerator), "GenerateDone", (Type[])null, (Type[])null) }; foreach (MethodBase methodBase in array) { Patches patchInfo2 = Harmony.GetPatchInfo(methodBase); if (patchInfo2 == null || !patchInfo2.Postfixes.Any((Patch patch) => patch.owner == owner)) { throw new InvalidOperationException("Missing valuable audit patch: " + methodBase.Name); } } } [HarmonyPrefix] private static void BeginGeneration(object __instance) { Sessions.GetValue(__instance, CreateSession); } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return CategoryLimitTranspiler.Rewrite(ChanceRollTranspiler.Rewrite(instructions, AccessTools.Method(typeof(Random), "Range", new Type[2] { typeof(int), typeof(int) }, (Type[])null), AccessTools.Method(typeof(ValuableSpawnPatch), "Roll", (Type[])null, (Type[])null)), AccessTools.Method(typeof(ValuableSpawnPatch), "PrepareCategories", (Type[])null, (Type[])null)); } private static void PrepareCategories(object coroutine) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown GenerationAudit value = Sessions.GetValue(coroutine, CreateSession); int[] array = (int[])LimitsField.GetValue(coroutine); List<ValuableVolume>[] array2 = (List<ValuableVolume>[])VolumesField.GetValue(coroutine); List<PrefabRef>[] array3 = (List<PrefabRef>[])ValuablesField.GetValue(coroutine); ValuableDirector obj = (ValuableDirector)DirectorField.GetValue(coroutine); int num = (int)TotalLimitField.GetValue(obj); bool isGameplay = SemiFunc.RunIsLevel(); int num2 = num; num = value.Weights.GenerationLimit(num, array2.Select((List<ValuableVolume> list) => list.Count).ToArray(), array3.Select((List<PrefabRef> list) => list.Count).ToArray(), isGameplay); if (num != num2) { TotalLimitField.SetValue(obj, num); } value.PrepareBudget(num2, num, (float)ValueLimitField.GetValue(obj)); for (int num3 = 0; num3 < array.Length; num3++) { int vanillaLimit = array[num3]; array[num3] = value.Weights.CategoryLimit(num3, vanillaLimit, array2[num3].Count, array3[num3].Count, num); } } private static int Roll(int minInclusive, int maxExclusive, int category, object coroutine) { return Sessions.GetValue(coroutine, CreateSession).Weights.Roll(minInclusive, maxExclusive, category, VanillaRandomRange); } internal static void RecordSpawn(ValuableDirector director, int category, string prefab, int amount, float currentValue, float valueLimit) { if (Directors.TryGetValue(director, out GenerationAudit value)) { value.RecordSpawn(category, prefab, amount); value.RecordValue(currentValue, valueLimit); } } internal static GenerationAudit GetAudit(ValuableDirector director) { return Directors.GetValue(director, (ValuableDirector _) => new GenerationAudit(ValuableSpawnControllerPlugin.CaptureWeights(), generationEntered: false)); } } [HarmonyPatch(typeof(ValuableDirector), "SpawnValuable")] internal static class ValuableSpawnAuditPatch { [HarmonyPrefix] private static void Prefix(int ___valuableTargetAmount, out int __state) { __state = ___valuableTargetAmount; } [HarmonyPostfix] private static void Postfix(ValuableDirector __instance, PrefabRef _valuable, ValuableVolume _volume, int ___valuableTargetAmount, int __state, float ___totalCurrentValue, float ___totalMaxValue) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected I4, but got Unknown int num = ___valuableTargetAmount - __state; if (num > 0) { ValuableSpawnPatch.RecordSpawn(__instance, (int)_volume.VolumeType, ((PrefabRef<GameObject>)(object)_valuable).ResourcePath, num, ___totalCurrentValue, ___totalMaxValue); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class ValuableCensusPatch { [HarmonyPostfix] private static void Postfix(LevelGenerator __instance) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected I4, but got Unknown if (!__instance.Generated || !SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsLevel()) { return; } try { GenerationAudit audit = ValuableSpawnPatch.GetAudit(ValuableDirector.instance); if (audit.Completed) { return; } ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>(); foreach (ValuableObject val in array) { audit.RecordObserved(((Object)val).GetInstanceID(), (int)val.volumeType, ((Object)val).name); } if (audit.TryComplete()) { ManualLogSource runtimeLog = ValuableSpawnControllerPlugin.RuntimeLog; string text = (Object.op_Implicit((Object)(object)__instance.Level) ? ((Object)__instance.Level).name : "(unknown)"); runtimeLog.LogInfo((object)($"[VSC Summary] Level={text}; Mode={audit.Weights.Mode}; Spawned={audit.Spawned.Total}; Observed={audit.Observed.Total}; " + $"Small={audit.Observed.GroupCount(SizeGroup.Small)}; Medium={audit.Observed.GroupCount(SizeGroup.Medium)}; Large={audit.Observed.GroupCount(SizeGroup.Large)}; " + $"ValueTotal={audit.CurrentValue * 1000f:0}; ValueBudget={audit.ValueLimit * 1000f:0}; StopReason={audit.StopReason}")); if (!audit.GenerationEntered) { runtimeLog.LogWarning((object)"[VSC Census] Generation entry was not observed; reported weights are current settings, not a confirmed generation snapshot."); } if (audit.Spawned.ZeroWeight > 0) { runtimeLog.LogError((object)$"[VSC Census] {audit.Spawned.ZeroWeight} normal spawns used a zero-weight category."); } else if (audit.Observed.ZeroWeight > 0) { runtimeLog.LogWarning((object)$"[VSC Census] {audit.Observed.ZeroWeight} active valuables have a zero-weight size. Check category assignments and other sources of valuables."); } } } catch (Exception arg) { ValuableSpawnControllerPlugin.RuntimeLog.LogError((object)$"[VSC Census] Count failed: {arg}"); } } } }