using System; using System.Collections; 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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [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 RepoDifficultyConfigurator { [HarmonyPatch(typeof(LevelGenerator), "TileGeneration")] internal static class LevelGenerator_TileGeneration_Patch { private static readonly FieldInfo ModuleAmount = AccessTools.Field(typeof(LevelGenerator), "ModuleAmount"); private static readonly FieldInfo LevelGrid = AccessTools.Field(typeof(LevelGenerator), "LevelGrid"); private static void Postfix(LevelGenerator __instance, ref IEnumerator __result) { } private static IEnumerator AddRoomsAfterTileGeneration(LevelGenerator generator, IEnumerator original) { while (original.MoveNext()) { yield return original.Current; } try { if (!(LevelGrid.GetValue(generator) is Array grid)) { yield break; } int addedRooms = 0; for (int i = 0; i < Plugin.AdditionalRooms.Value; i++) { List candidates = new List(); for (int x = 0; x < generator.LevelWidth; x++) { for (int y = 0; y < generator.LevelHeight; y++) { object tile = grid.GetValue(x, y); if (tile != null && !IsTileActive(tile) && IsAdjacentToActive(grid, generator.LevelWidth, generator.LevelHeight, x, y)) { candidates.Add(tile); } } } if (candidates.Count == 0) { break; } object tileToAdd = candidates[Random.Range(0, candidates.Count)]; SetTileField(tileToAdd, "active", true); SetTileField(tileToAdd, "type", 0); addedRooms++; } int addedExtractionRooms = EnforceExtractionTopology(grid, generator.LevelWidth, generator.LevelHeight); int addedTotal = addedRooms + addedExtractionRooms; if (addedTotal > 0) { ModuleAmount.SetValue(generator, (int)ModuleAmount.GetValue(generator) + addedTotal); } Plugin.Log.LogInfo((object)("Rooms: added " + addedRooms + " of " + Plugin.AdditionalRooms.Value + " requested connected rooms.")); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not add rooms: " + ex)); } } private static bool IsAdjacentToActive(Array grid, int width, int height, int x, int y) { return IsActive(grid, width, height, x + 1, y) || IsActive(grid, width, height, x - 1, y) || IsActive(grid, width, height, x, y + 1) || IsActive(grid, width, height, x, y - 1); } private static bool IsActive(Array grid, int width, int height, int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) { return false; } object value = grid.GetValue(x, y); return value != null && IsTileActive(value); } private static bool IsTileActive(object tile) { return (bool)AccessTools.Field(tile.GetType(), "active").GetValue(tile); } private static void SetTileField(object tile, string name, object value) { FieldInfo fieldInfo = AccessTools.Field(tile.GetType(), name); fieldInfo.SetValue(tile, fieldInfo.FieldType.IsEnum ? Enum.ToObject(fieldInfo.FieldType, value) : value); } private static int EnforceExtractionTopology(Array grid, int width, int height) { List list = new List(); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { object value = grid.GetValue(i, j); if (value != null && IsTileActive(value) && GetTileType(value) == 3) { list.Add(value); } } } int num = list.Count + Plugin.AdditionalExtractionPoints.Value; foreach (object item in list) { if (ActiveNeighbourCount(grid, width, height, GetTileInt(item, "x"), GetTileInt(item, "y")) != 1) { SetTileField(item, "type", 0); } } int k; for (k = CountExtractionTiles(grid, width, height); k < num; k++) { object obj = null; for (int l = 0; l < width; l++) { if (obj != null) { break; } for (int m = 0; m < height; m++) { object value2 = grid.GetValue(l, m); if (value2 != null && IsTileActive(value2) && !GetTileBool(value2, "first") && GetTileType(value2) != 3 && ActiveNeighbourCount(grid, width, height, l, m) == 1) { obj = value2; break; } } } if (obj == null) { break; } SetTileField(obj, "type", 3); } int num2 = 0; while (k < num) { object obj2 = null; for (int n = 0; n < width; n++) { if (obj2 != null) { break; } for (int num3 = 0; num3 < height; num3++) { object value3 = grid.GetValue(n, num3); if (value3 != null && !IsTileActive(value3) && ActiveNeighbourCount(grid, width, height, n, num3) == 1) { obj2 = value3; break; } } } if (obj2 == null) { break; } SetTileField(obj2, "active", true); SetTileField(obj2, "type", 3); k++; num2++; } Plugin.Log.LogInfo((object)("Extraction points: " + k + " of " + num + " placed on one-entrance edge rooms. Quota unchanged.")); return num2; } private static int CountExtractionTiles(Array grid, int width, int height) { int num = 0; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { object value = grid.GetValue(i, j); if (value != null && IsTileActive(value) && GetTileType(value) == 3) { num++; } } } return num; } private static int ActiveNeighbourCount(Array grid, int width, int height, int x, int y) { int num = 0; if (IsActive(grid, width, height, x + 1, y)) { num++; } if (IsActive(grid, width, height, x - 1, y)) { num++; } if (IsActive(grid, width, height, x, y + 1)) { num++; } if (IsActive(grid, width, height, x, y - 1)) { num++; } return num; } private static int GetTileType(object tile) { return Convert.ToInt32(AccessTools.Field(tile.GetType(), "type").GetValue(tile)); } private static int GetTileInt(object tile, string name) { return (int)AccessTools.Field(tile.GetType(), name).GetValue(tile); } private static bool GetTileBool(object tile, string name) { return (bool)AccessTools.Field(tile.GetType(), name).GetValue(tile); } private static bool IsHostOrSingle() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return true; } } } [HarmonyPatch] internal static class LevelGenerator_TileGeneration_Coroutine_Patch { private static readonly FieldInfo ModuleAmount = AccessTools.Field(typeof(LevelGenerator), "ModuleAmount"); private static readonly FieldInfo ExtractionAmount = AccessTools.Field(typeof(LevelGenerator), "ExtractionAmount"); private static MethodBase TargetMethod() { Type type = typeof(LevelGenerator).GetNestedTypes(BindingFlags.NonPublic).FirstOrDefault((Type type2) => type2.Name.StartsWith("d__", StringComparison.Ordinal)); return (type == null) ? null : AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } private static IEnumerable Transpiler(IEnumerable instructions) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown List list = instructions.ToList(); MethodInfo methodInfo = AccessTools.Method(typeof(LevelGenerator_TileGeneration_Coroutine_Patch), "ApplyRoomCount", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(LevelGenerator_TileGeneration_Coroutine_Patch), "ApplyExtractionCount", (Type[])null, (Type[])null); int num = list.FindLastIndex((CodeInstruction code) => code.opcode == OpCodes.Stfld && (code.operand as FieldInfo)?.Name == "ModuleAmount"); if (num >= 0) { list.Insert(num + 1, new CodeInstruction(OpCodes.Ldloc_1, (object)null)); list.Insert(num + 2, new CodeInstruction(OpCodes.Call, (object)methodInfo)); } int num2 = list.FindIndex((CodeInstruction code) => code.opcode == OpCodes.Call && (code.operand as MethodInfo)?.Name == "IsLevelShop"); if (num2 >= 0) { list.Insert(num2, new CodeInstruction(OpCodes.Ldloc_1, (object)null)); list.Insert(num2 + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo2)); } return list; } private static void ApplyRoomCount(LevelGenerator generator) { if (IsHostOrSingle() && Plugin.AdditionalRooms.Value > 0) { int num = (int)ModuleAmount.GetValue(generator); ModuleAmount.SetValue(generator, num + Plugin.AdditionalRooms.Value); Plugin.Log.LogInfo((object)("Rooms: " + num + " -> " + (num + Plugin.AdditionalRooms.Value) + ".")); } } private static void ApplyExtractionCount(LevelGenerator generator) { if (IsHostOrSingle() && Plugin.AdditionalExtractionPoints.Value > 0) { int num = (int)ExtractionAmount.GetValue(generator); ExtractionAmount.SetValue(generator, num + Plugin.AdditionalExtractionPoints.Value); Plugin.Log.LogInfo((object)("Extraction points: " + num + " -> " + (num + Plugin.AdditionalExtractionPoints.Value) + ". Quota unchanged.")); } } private static bool IsHostOrSingle() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return true; } } } [HarmonyPatch(typeof(ValuableDirector), "SpawnValuable")] internal static class ValuableDirector_SpawnValuable_Patch { private static readonly FieldInfo TotalMaxValue = AccessTools.Field(typeof(ValuableDirector), "totalMaxValue"); private static readonly FieldInfo TotalMaxAmount = AccessTools.Field(typeof(ValuableDirector), "totalMaxAmount"); private static readonly FieldInfo LevelGeneratorInstance = AccessTools.Field(typeof(LevelGenerator), "Instance"); private static readonly FieldInfo ModuleAmount = AccessTools.Field(typeof(LevelGenerator), "ModuleAmount"); private static readonly FieldInfo ExtractionAmount = AccessTools.Field(typeof(LevelGenerator), "ExtractionAmount"); private static readonly HashSet AdjustedDirectors = new HashSet(); private static void Prefix(ValuableDirector __instance) { if ((Object)(object)__instance == (Object)null || !IsHostOrSingle() || !Plugin.ScaleValuableLoot.Value || AdjustedDirectors.Contains(__instance)) { return; } try { object? obj = LevelGeneratorInstance?.GetValue(null); LevelGenerator val = (LevelGenerator)((obj is LevelGenerator) ? obj : null); if (!((Object)(object)val == (Object)null) && !(TotalMaxValue == null) && !(TotalMaxAmount == null)) { int num = Math.Max(1, ReadInt(ModuleAmount, val)); int val2 = Math.Max(1, ReadInt(ExtractionAmount, val)); int num2 = Math.Max(val2, (int)Math.Ceiling((double)num / 6.0)); int num3 = Math.Min(Plugin.MinimumValuePerQuota.Value, Plugin.MaximumValuePerQuota.Value); int num4 = Math.Max(Plugin.MinimumValuePerQuota.Value, Plugin.MaximumValuePerQuota.Value); int num5 = Random.Range(num3, num4 + 1); int num6 = num2 * num5; int num7 = Math.Max(1, ReadInt(TotalMaxValue, __instance)); int num8 = Math.Max(1, ReadInt(TotalMaxAmount, __instance)); float num9 = (float)num6 / (float)num7; WriteNumber(TotalMaxValue, __instance, num6); WriteNumber(TotalMaxAmount, __instance, Math.Max(num8, (int)Math.Ceiling((float)num8 * num9))); AdjustedDirectors.Add(__instance); Plugin.Log.LogInfo((object)("Loot target: " + num6 + " (" + num2 + " quota units × " + num5 + "). Rooms: " + num + ", extraction points: " + val2 + ".")); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not scale valuable loot: " + ex)); } } private static int ReadInt(FieldInfo field, object instance) { return Convert.ToInt32(field.GetValue(instance)); } private static void WriteNumber(FieldInfo field, object instance, int value) { Type fieldType = field.FieldType; field.SetValue(instance, (fieldType == typeof(float)) ? ((object)(float)value) : ((fieldType == typeof(double)) ? ((object)(double)value) : ((fieldType == typeof(long)) ? ((object)(long)value) : ((fieldType == typeof(decimal)) ? ((object)(decimal)value) : ((object)value))))); } private static bool IsHostOrSingle() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return true; } } } [HarmonyPatch(typeof(EnemyDirector), "AmountSetup")] internal static class EnemyDirector_AmountSetup_Patch { private static readonly FieldInfo TotalAmount = AccessTools.Field(typeof(EnemyDirector), "totalAmount"); private static readonly FieldInfo EnemyList = AccessTools.Field(typeof(EnemyDirector), "enemyList"); private static readonly FieldInfo EnemyListCurrent = AccessTools.Field(typeof(EnemyDirector), "enemyListCurrent"); private static void Postfix(EnemyDirector __instance) { if ((Object)(object)__instance == (Object)null || !IsHostOrSingle()) { return; } try { string details; int num = DuplicateVanillaSelections(__instance, out details); if (num != 0) { TotalAmount?.SetValue(__instance, (int)TotalAmount.GetValue(__instance) + num); Plugin.Log.LogInfo((object)("Added " + num + " configured copies of monster types selected by vanilla generation.")); Plugin.Log.LogInfo((object)("Monster matching: " + details)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not add configured monsters: " + ex)); } } private static int DuplicateVanillaSelections(EnemyDirector director, out string details) { details = "no vanilla enemy selections"; List list = EnemyList?.GetValue(director) as List; List list2 = EnemyListCurrent?.GetValue(director) as List; if (list == null || list2 == null) { return 0; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (EnemySetup item in list) { string spawnName = GetSpawnName(item); if (!string.IsNullOrEmpty(spawnName) && !dictionary.ContainsKey(Plugin.Normalize(spawnName))) { dictionary[Plugin.Normalize(spawnName)] = item; } } int num = 0; List list3 = new List(); foreach (KeyValuePair item2 in dictionary) { EnemySetup value = item2.Value; string spawnName2 = GetSpawnName(value); int addition = Plugin.GetAddition(spawnName2); list3.Add(spawnName2 + " => " + Plugin.Normalize(spawnName2) + " (" + addition + ")"); for (int i = 0; i < addition; i++) { if (value.spawnObjects != null && value.spawnObjects.Count != 0 && value.spawnObjects[0] != null) { list.Add(value); list2.Add(value); num++; } } } details = ((list3.Count == 0) ? "no selected monster types" : string.Join("; ", list3)); return num; } private static string GetSpawnName(EnemySetup setup) { if ((Object)(object)setup == (Object)null || setup.spawnObjects == null || setup.spawnObjects.Count == 0) { return null; } PrefabRef val = setup.spawnObjects[0]; if (val == null) { return null; } if (!string.IsNullOrWhiteSpace(((PrefabRef)(object)val).ResourcePath)) { return ((PrefabRef)(object)val).ResourcePath; } Type type = ((object)val).GetType(); MemberInfo memberInfo = (MemberInfo)(((object)type.GetProperty("PrefabName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) ?? ((object)type.GetField("PrefabName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))); return (!(memberInfo is PropertyInfo propertyInfo)) ? ((!(memberInfo is FieldInfo fieldInfo)) ? ((object)val).ToString() : fieldInfo.GetValue(val)?.ToString()) : propertyInfo.GetValue(val)?.ToString(); } private static bool IsHostOrSingle() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return true; } } } [BepInPlugin("N8917.RepoDifficultyConfigurator", "Difficulty Editor", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "N8917.RepoDifficultyConfigurator"; public const string PluginName = "Difficulty Editor"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; internal static ConfigEntry AdditionalRooms; internal static ConfigEntry AdditionalExtractionPoints; internal static ConfigEntry ScaleValuableLoot; internal static ConfigEntry MinimumValuePerQuota; internal static ConfigEntry MaximumValuePerQuota; internal static readonly Dictionary> EnemyAdds = new Dictionary>(StringComparer.OrdinalIgnoreCase); private Harmony harmony; private static readonly (string Tier, string Name)[] Enemies = new(string, string)[29] { ("Monsters — Tier 1", "Apex Predator"), ("Monsters — Tier 1", "Bella"), ("Monsters — Tier 1", "Birthday Boy"), ("Monsters — Tier 1", "Elsa"), ("Monsters — Tier 1", "Gnome"), ("Monsters — Tier 1", "Peeper"), ("Monsters — Tier 1", "Shadow Child"), ("Monsters — Tier 1", "Spewer"), ("Monsters — Tier 1", "Tick"), ("Monsters — Tier 2", "Animal"), ("Monsters — Tier 2", "Banger"), ("Monsters — Tier 2", "Bowtie"), ("Monsters — Tier 2", "Chef"), ("Monsters — Tier 2", "Gambit"), ("Monsters — Tier 2", "Headgrab"), ("Monsters — Tier 2", "Heart Hugger"), ("Monsters — Tier 2", "Hidden"), ("Monsters — Tier 2", "Mentalist"), ("Monsters — Tier 2", "Oogley"), ("Monsters — Tier 2", "Rugrat"), ("Monsters — Tier 2", "Upscream"), ("Monsters — Tier 3", "Cleanup Crew"), ("Monsters — Tier 3", "Headman"), ("Monsters — Tier 3", "Huntsman"), ("Monsters — Tier 3", "Loom"), ("Monsters — Tier 3", "Robe"), ("Monsters — Tier 3", "Trudge"), ("Monsters — Tier 3", "Reaper"), ("Monsters — Tier 3", "Clown") }; private void Awake() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; AcceptableValueRange val = new AcceptableValueRange(0, 100); AdditionalRooms = ((BaseUnityPlugin)this).Config.Bind("Level", "Additional Rooms", 0, new ConfigDescription("Extra rooms added to every generated level. 0 keeps the vanilla amount.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 50), Array.Empty())); AdditionalExtractionPoints = ((BaseUnityPlugin)this).Config.Bind("Level", "Additional Extraction Points", 0, new ConfigDescription("Extra physical extraction points, not quota money. 0 keeps the vanilla amount.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); ScaleValuableLoot = ((BaseUnityPlugin)this).Config.Bind("Loot", "Scale Valuable Loot", true, "Sets the level's valuable-loot target according to its rooms and extraction points."); MinimumValuePerQuota = ((BaseUnityPlugin)this).Config.Bind("Loot", "Minimum Value Per Quota", 35000, new ConfigDescription("Minimum valuable-loot target for each quota unit.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 1000000), Array.Empty())); MaximumValuePerQuota = ((BaseUnityPlugin)this).Config.Bind("Loot", "Maximum Value Per Quota", 42000, new ConfigDescription("Maximum valuable-loot target for each quota unit.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 1000000), Array.Empty())); (string, string)[] enemies = Enemies; for (int i = 0; i < enemies.Length; i++) { (string, string) tuple = enemies[i]; EnemyAdds[tuple.Item2] = ((BaseUnityPlugin)this).Config.Bind(tuple.Item1, "Additional " + tuple.Item2, 0, new ConfigDescription("Additional " + tuple.Item2 + " enemies. 0 keeps the vanilla amount.", (AcceptableValueBase)(object)val, Array.Empty())); } harmony = new Harmony("N8917.RepoDifficultyConfigurator"); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Difficulty Editor 1.0.0 loaded."); } internal static int GetAddition(string name) { if (string.IsNullOrWhiteSpace(name)) { return 0; } string text = Normalize(name); foreach (KeyValuePair> enemyAdd in EnemyAdds) { if (Normalize(enemyAdd.Key) == text) { return Math.Max(0, enemyAdd.Value.Value); } } return 0; } internal static string Normalize(string value) { string text = value.Replace("(Clone)", "").Trim(); int num = Math.Max(text.LastIndexOf('/'), text.LastIndexOf('\\')); if (num >= 0) { text = text.Substring(num + 1); } if (text.StartsWith("Enemy", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(5).TrimStart(' ', '-'); } text = text.Replace(" ", "").Replace("_", "").Replace("-", "") .ToLowerInvariant(); if (1 == 0) { } string result = text switch { "oogly" => "oogley", "gumbit" => "gambit", "animal" => "animal", "bangdirector" => "banger", "beamer" => "clown", "birthdayboy" => "birthdayboy", "bombthrower" => "cleanupcrew", "bowtie" => "bowtie", "ceilingeye" => "peeper", "duck" => "apexpredator", "elsa" => "elsa", "floater" => "mentalist", "gnomedirector" => "gnome", "head" => "headman", "headgrabber" => "headgrab", "hearthugger" => "hearthugger", "hidden" => "hidden", "hunter" => "huntsman", "robe" => "robe", "runner" => "reaper", "shadow" => "loom", "slowmouth" => "spewer", "slowwalker" => "trudge", "spinny" => "gambit", "thinman" => "shadowchild", "tick" => "tick", "tricycle" => "bella", "tumbler" => "chef", "upscream" => "upscream", "valuablethrower" => "rugrat", _ => text, }; if (1 == 0) { } return result; } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } }