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 { 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 Enabled; internal static ConfigEntry ModdedChancePerSlot; internal static ConfigEntry RethemeChance; internal static ConfigEntry Verbose; internal static readonly Dictionary PrefabToBundle = new Dictionary(new RefComparer()); internal static readonly HashSet SprinkleCats = new HashSet(); internal static readonly Dictionary[]> SprinkleBySize = new Dictionary[]>(); internal static readonly Dictionary[]> PresetNativeBySize = new Dictionary[]>(); internal static bool PoolsBuilt; internal static object CurrentLevel; internal static List[] NativeNow = NewPools(); internal static bool RethemeActive; internal static string RethemeCategory; internal static List[] NewPools() { List[] array = new List[7]; for (int i = 0; i < 7; i++) { array[i] = new List(); } 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("Balance", "Enabled", true, "Master switch."); ModdedChancePerSlot = ((BaseUnityPlugin)this).Config.Bind("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("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("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 index) { try { AssetBundle bundle = ((PrefabRef)(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 index = BuildAssetBundleIndex(); List list = (from lv in Resources.FindObjectsOfTypeAll() 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[] array = Lists(item); foreach (List 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[] array = Lists(item3); foreach (List 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 hashSet = new HashSet(new Plugin.RefComparer()); foreach (LevelValuables item5 in list) { List[] array2 = Plugin.NewPools(); Plugin.PresetNativeBySize[item5] = array2; List[] 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[] p) => p.Sum((List 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 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[] array = Plugin.NewPools(); try { if ((Object)(object)lvl != (Object)null && lvl.ValuablePresets != null) { HashSet hashSet = new HashSet(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[] value2; List 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)(object)val).Prefab; } catch { } if ((Object)(object)val2 != (Object)null) { _valuable = val; } } } catch { } } private static void HealthCheck() { int num = Plugin.PresetNativeBySize.Values.Sum((List[] p) => p.Sum((List 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[] value; int num2 = (Plugin.SprinkleBySize.TryGetValue(item, out value) ? value.Sum((List 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 seen = new HashSet(new Plugin.RefComparer()); int checkedCount = 0; int unresolved = 0; List broken = new List(); foreach (KeyValuePair[]> item2 in Plugin.SprinkleBySize) { Scan(item2.Value, item2.Key); } foreach (KeyValuePair[]> 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[] 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)(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(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[] Lists(LevelValuables lv) { return new List[7] { lv.tiny, lv.small, lv.medium, lv.big, lv.wide, lv.tall, lv.veryTall }; } private static PrefabRef RandomFrom(List list) { if (list == null || list.Count == 0) { return null; } return list[Random.Range(0, list.Count)]; } private static Dictionary BuildAssetBundleIndex() { Dictionary dictionary = new Dictionary(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 index) { try { string text = LeafName(((PrefabRef)(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) { } } }