using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; [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("EasyCampaignTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("EasyCampaignTweaks")] [assembly: AssemblyTitle("EasyCampaignTweaks")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 EasyCampaignTweaks { [BepInPlugin("twistedscarlett.ultrakill.easycampaigntweaks", "Easy Campaign Tweaks", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class EasyCampaignTweaksPlugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__108 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public GameObject source; public EasyCampaignTweaksPlugin <>4__this; public GameObject clone; public string key; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__108(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int num = <>1__state; EasyCampaignTweaksPlugin easyCampaignTweaksPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.2f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)source == (Object)null) { return false; } if (easyCampaignTweaksPlugin.IsSpawnedCloneValid(clone, key)) { Object.Destroy((Object)(object)source); } else if ((Object)(object)clone != (Object)null) { Object.Destroy((Object)(object)clone); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PluginGuid = "twistedscarlett.ultrakill.easycampaigntweaks"; public const string PluginName = "Easy Campaign Tweaks"; public const string PluginVersion = "1.0.2"; internal static EasyCampaignTweaksPlugin Instance; internal static ManualLogSource Log; private Harmony _harmony; private Type _enemyIdentifierType; private Type _gunControlType; private Type _dualWieldType; private Type _newMovementType; private Component _cachedMovement; private Rigidbody _cachedMovementRigidbody; private readonly Dictionary _fieldCache = new Dictionary(); private readonly Dictionary _propertyCache = new Dictionary(); private readonly Dictionary _methodCache = new Dictionary(); private ConfigEntry _debugLogging; private ConfigEntry _spawnDistanceMin; private ConfigEntry _spawnDistanceMax; private ConfigEntry _mitosisEnabled; private ConfigEntry _mitosisMultiplier; private ConfigEntry _enrageEnabled; private ConfigEntry _enrageChancePercent; private ConfigEntry _radianceEnabled; private ConfigEntry _radianceChancePercent; private ConfigEntry _radianceTier; private ConfigEntry _radianceUseCustomMultipliers; private ConfigEntry _radianceSpeedMultiplier; private ConfigEntry _radianceHealthMultiplier; private ConfigEntry _radianceDamageMultiplier; private ConfigEntry _radianceAffectsDamage; private ConfigEntry _sandifyEnabled; private ConfigEntry _sandifyChancePercent; private ConfigEntry _tankifyEnabled; private ConfigEntry _tankifyChancePercent; private ConfigEntry _tankifyHealthMultiplier; private ConfigEntry _strongerEnabled; private ConfigEntry _strongerChancePercent; private ConfigEntry _strongerMinimumTierIncrease; private ConfigEntry _strongerMaximumTierIncrease; private ConfigEntry _strongerMaxReplacementsPerWave; private ConfigEntry _strongerAntiSoftlockFilthCount; private ConfigEntry _randomizerEnabled; private ConfigEntry _randomizerChancePercent; private ConfigEntry _maxRandomizerReplacementsPerWave; private ConfigEntry _randomizerAntiSoftlockFilthCount; private ConfigEntry _ultrahotEnabled; private ConfigEntry _ultrahotVelocityForNormalTime; private ConfigEntry _ultrahotMinimumTimeScale; private ConfigEntry _ultrahotMaximumTimeScale; private ConfigEntry _ultrahotSmoothing; private ConfigEntry _dualWieldEnabled; private ConfigEntry _dualWieldStacks; private ConfigEntry _dualWieldDuration; private ConfigEntry _dualWieldRefreshSeconds; private readonly HashSet _processedEnemies = new HashSet(); private readonly List _pendingWave = new List(); private readonly Dictionary _scenePools = new Dictionary(); private readonly Dictionary _globalPools = new Dictionary(); private readonly HashSet _loggedBlocked = new HashSet(); private float _pendingWaveDeadline = -1f; private float _sceneLoadedTime; private string _currentSceneName = string.Empty; private bool _ultrahotWasActive; private bool _globalPoolScanned; private float _nextDualWieldRefresh; private const int DefaultMaxLivingModdedEnemies = 96; private const bool AffectCyberGrind = false; private const float SceneStartGraceSeconds = 1f; private const float SpawnWaveReadSeconds = 1.25f; private const float LowTierSpawnWaveReadSeconds = 0.45f; private const int ProcessWaveImmediatelyAtSize = 5; private const int LargeEnemyMinimumNonFilthWaveSize = 5; private const float LargeEnemyRarityMultiplier = 0.5f; private const float LargeEnemyGroundCheckDistance = 10f; private void Awake() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; CacheCommonTypes(); BindConfig(); _sceneLoadedTime = Time.time; Scene activeScene = SceneManager.GetActiveScene(); _currentSceneName = ((Scene)(ref activeScene)).name ?? string.Empty; SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("twistedscarlett.ultrakill.easycampaigntweaks"); PatchSafely(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Easy Campaign Tweaks loaded. Gameplay modifiers are disabled by default."); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; if (_ultrahotWasActive) { Time.timeScale = 1f; } try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to unpatch cleanly: " + ex.Message)); } } private void CacheCommonTypes() { _enemyIdentifierType = AccessTools.TypeByName("EnemyIdentifier"); _gunControlType = AccessTools.TypeByName("GunControl"); _dualWieldType = AccessTools.TypeByName("DualWield"); _newMovementType = AccessTools.TypeByName("NewMovement"); } private ConfigEntry Bind(string section, string key, T defaultValue, string description) { return ((BaseUnityPlugin)this).Config.Bind(section, key, defaultValue, description); } private void BindConfig() { _spawnDistanceMin = Bind("Mitosis", "SpawnDistanceMin", 2f, "Minimum offset used for Mitosis duplicates."); _spawnDistanceMax = Bind("Mitosis", "SpawnDistanceMax", 5.5f, "Maximum offset used for Mitosis duplicates."); _mitosisEnabled = Bind("Mitosis", "Enabled", defaultValue: false, "Duplicate eligible enemy spawns."); _mitosisMultiplier = Bind("Mitosis", "EnemySpawnMultiplier", 2f, "Total enemy spawn multiplier. 2.0 means one extra copy per natural enemy on average."); _enrageEnabled = Bind("Enraged", "Enabled", defaultValue: false, "Enable Enraged module."); _enrageChancePercent = Bind("Enraged", "EnrageChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to become enraged."); _radianceEnabled = Bind("Radiant", "Enabled", defaultValue: false, "Enable Radiant module."); _radianceChancePercent = Bind("Radiant", "RadiantChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to receive radiance."); _radianceTier = Bind("Radiant", "RadianceTier", 1f, "Radiance tier. Default 1 is tier-1 radiance. Values above 1 increase the game's radiance tier scaling."); _radianceUseCustomMultipliers = Bind("Radiant", "UseCustomMultipliers", defaultValue: false, "If false, use vanilla-style tier-1 per-enemy radiance presets. If true, use the custom Speed/Health/Damage multipliers below."); _radianceSpeedMultiplier = Bind("Radiant", "CustomSpeedMultiplier", 1.5f, "Only used when UseCustomMultipliers is true."); _radianceHealthMultiplier = Bind("Radiant", "CustomHealthMultiplier", 1.5f, "Only used when UseCustomMultipliers is true."); _radianceDamageMultiplier = Bind("Radiant", "CustomDamageMultiplier", 1.5f, "Only used when UseCustomMultipliers is true and RadianceAffectsDamage is enabled."); _radianceAffectsDamage = Bind("Radiant", "RadianceAffectsDamage", defaultValue: true, "If true, radiant enemies receive the damage buff. Disable for Cyber Grind-style no-damage radiance."); _sandifyEnabled = Bind("Sandify", "Enabled", defaultValue: false, "Enable Sandify module."); _sandifyChancePercent = Bind("Sandify", "SandifyChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to spawn sanded."); _tankifyEnabled = Bind("Tankify", "Enabled", defaultValue: false, "Enable Tankify module."); _tankifyChancePercent = Bind("Tankify", "TankifyChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to have altered health."); _tankifyHealthMultiplier = Bind("Tankify", "HealthMultiplier", 2f, "Enemy health multiplier. Values below 1 make enemies frailer; values above 1 make them tankier."); _strongerEnabled = Bind("Stronger Enemies", "Enabled", defaultValue: false, "Replace eligible enemies with stronger enemies from the protected global pool."); _strongerChancePercent = Bind("Stronger Enemies", "ReplacementChancePercent", 100f, "Chance from 0 to 100 for each eligible enemy in a wave to be considered for a stronger replacement."); _strongerMinimumTierIncrease = Bind("Stronger Enemies", "MinimumTierIncrease", 1, "Minimum tier increase. Default 1 means at least the next stronger tier."); _strongerMaximumTierIncrease = Bind("Stronger Enemies", "MaximumTierIncrease", 1, "Preferred maximum tier increase. If this is higher than the minimum, the target tier jump is randomized."); _strongerMaxReplacementsPerWave = Bind("Stronger Enemies", "MaxReplacementsPerWave", 25, "Hard cap for Stronger Enemies replacements in a single spawn wave."); _strongerAntiSoftlockFilthCount = Bind("Stronger Enemies", "AntiSoftlockFilth", 2, "After this module replaces enemies in a wave of 3+ natural enemies, spawn this many extra Filth as softlock insurance. Set to 0 to disable."); _randomizerEnabled = Bind("Safe Randomizer", "Enabled", defaultValue: false, "Randomize natural enemy spawns using protected templates. Malicious Faces are always protected."); _randomizerChancePercent = Bind("Safe Randomizer", "ReplacementChancePercent", 100f, "Chance from 0 to 100 for each eligible natural enemy in a wave to be replaced."); _maxRandomizerReplacementsPerWave = Bind("Safe Randomizer", "MaxReplacementsPerWave", 25, "Hard cap for Safe Randomizer replacements in a single wave."); _randomizerAntiSoftlockFilthCount = Bind("Safe Randomizer", "AntiSoftlockFilth", 2, "After this module replaces enemies in a wave of 3+ natural enemies, spawn this many extra Filth as softlock insurance. Set to 0 to disable."); _ultrahotEnabled = Bind("ULTRAHOT", "Enabled", defaultValue: false, "Enable ULTRAHOT: time moves based on player movement speed."); _ultrahotVelocityForNormalTime = Bind("ULTRAHOT", "VelocityForNormalTime", 20f, "Player velocity magnitude that maps to normal time scale."); _ultrahotMinimumTimeScale = Bind("ULTRAHOT", "MinimumTimeScale", 0.01f, "Slowest time scale while nearly still."); _ultrahotMaximumTimeScale = Bind("ULTRAHOT", "MaximumTimeScale", 1.25f, "Fastest time scale while moving quickly."); _ultrahotSmoothing = Bind("ULTRAHOT", "Smoothing", 15f, "How quickly time scale follows player movement."); _dualWieldEnabled = Bind("Dual Wield", "Enabled", defaultValue: false, "Always keep at least the configured number of Dual Wield powerups active."); _dualWieldStacks = Bind("Dual Wield", "Stacks", 1, "Minimum number of Dual Wield stacks to keep active. Stacks can be very strong."); _dualWieldDuration = Bind("Dual Wield", "PowerupDurationSeconds", 999999f, "Duration used when creating a maintained Dual Wield stack."); _dualWieldRefreshSeconds = Bind("Dual Wield", "RefreshCheckSeconds", 0.75f, "How often the mod checks whether it needs to add missing Dual Wield stacks."); _debugLogging = Bind("Debug", "DebugLogging", defaultValue: false, "Verbose logs for spawn-wave decisions and skipped enemies."); } private void PatchSafely() { PatchFirstMethod(GetEnemyIdentifierType(), "Start", "EnemyStartPostfix", required: true); } private void PatchFirstMethod(Type type, string methodName, string patchMethodName, bool required) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown if (type == null) { if (required) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find type for required patch method " + methodName + ".")); } return; } MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { if (required) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find required method " + type.Name + "." + methodName + ".")); } } else { HarmonyMethod val = new HarmonyMethod(typeof(EasyCampaignTweaksPlugin).GetMethod(patchMethodName, BindingFlags.Static | BindingFlags.NonPublic)); _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + type.Name + "." + methodName + ".")); } } private static void EnemyStartPostfix(object __instance) { try { Instance?.OnEnemyStarted(__instance); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("Enemy Start patch failed safely: " + ex.GetType().Name + ": " + ex.Message)); } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { _sceneLoadedTime = Time.time; _currentSceneName = ((Scene)(ref scene)).name ?? string.Empty; _processedEnemies.Clear(); _pendingWave.Clear(); _pendingWaveDeadline = -1f; _scenePools.Clear(); _globalPools.Clear(); _globalPoolScanned = false; _cachedMovement = null; _cachedMovementRigidbody = null; _nextDualWieldRefresh = Time.time + 1f; if (_ultrahotWasActive) { Time.timeScale = 1f; } if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Scene loaded: " + _currentSceneName)); } } private void Update() { if (NeedsWaveDirector()) { ProcessPendingWaveIfReady(); } else if (_pendingWave.Count > 0) { _pendingWave.Clear(); _pendingWaveDeadline = -1f; } UpdateUltraHot(); UpdateDualWieldMaintenance(); } private bool CanModifyCurrentScene() { string text = (_currentSceneName ?? string.Empty).ToLowerInvariant(); if (text.Contains("endless") || text.Contains("cyber") || text.Contains("grid")) { return false; } return true; } private bool NeedsWaveDirector() { if (_strongerEnabled == null || !_strongerEnabled.Value) { if (_randomizerEnabled != null) { return _randomizerEnabled.Value; } return false; } return true; } private bool NeedsEnemyEffectModifiers() { if ((_sandifyEnabled == null || !_sandifyEnabled.Value) && (_tankifyEnabled == null || !_tankifyEnabled.Value) && (_radianceEnabled == null || !_radianceEnabled.Value)) { if (_enrageEnabled != null) { return _enrageEnabled.Value; } return false; } return true; } private void OnEnemyStarted(object enemyObject) { if (!CanModifyCurrentScene() || enemyObject == null) { return; } Component val = (Component)((enemyObject is Component) ? enemyObject : null); if ((Object)(object)val == (Object)null || (Object)(object)val.gameObject == (Object)null) { return; } GameObject gameObject = val.gameObject; if (!gameObject.activeInHierarchy) { return; } int instanceID = ((Object)gameObject).GetInstanceID(); if (_processedEnemies.Contains(instanceID)) { return; } _processedEnemies.Add(instanceID); if ((Object)(object)gameObject.GetComponent() != (Object)null || ((Object)gameObject).name.IndexOf("[ECM", StringComparison.OrdinalIgnoreCase) >= 0) { ApplyConfiguredEnemyEffects(gameObject, isModdedEnemy: true); return; } if (IsHardBlockedTemplateName(((Object)gameObject).name)) { LogBlocked(string.Empty, ((Object)gameObject).name, "hard-blocked name"); return; } string text = ReadEnemyTypeName(enemyObject, gameObject); EnemyProfile profile = EnemyCatalog.GetProfile(text, ((Object)gameObject).name, allowUnknown: false, allowExperimentalLarge: true); if (!profile.Allowed) { LogBlocked(text, ((Object)gameObject).name, profile.BlockReason); return; } if (NeedsWaveDirector()) { RegisterTemplate(_scenePools, profile, gameObject, allowInactiveTemplates: false); RegisterTemplate(_globalPools, profile, gameObject, allowInactiveTemplates: false); } ApplyConfiguredEnemyEffects(gameObject, isModdedEnemy: false); TryApplyImmediateMitosis(gameObject, profile); if (NeedsWaveDirector()) { QueueNaturalEnemyForWave(gameObject, profile); } } private void QueueNaturalEnemyForWave(GameObject source, EnemyProfile profile) { if (!((Object)(object)source == (Object)null) && profile.Allowed) { _pendingWave.Add(new PendingWaveEnemy(source, profile, Time.time)); RefreshPendingWaveDeadline(); } } private void RefreshPendingWaveDeadline() { if (_pendingWave.Count == 0) { _pendingWaveDeadline = -1f; return; } bool flag = true; for (int i = 0; i < _pendingWave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = _pendingWave[i]; if (pendingWaveEnemy == null || !IsFastFodder(pendingWaveEnemy.Profile.Key)) { flag = false; break; } } float num = Mathf.Clamp(1.25f, 0.05f, 6f); float num2 = Mathf.Clamp(0.45f, 0.05f, num); float num3 = (flag ? num2 : num); int num4 = Mathf.Max(2, 5); if (_pendingWave.Count >= num4) { num3 = Mathf.Min(num3, 0.12f); } _pendingWaveDeadline = Time.time + num3; } private bool IsFastFodder(string key) { key = Normalize(key); if (!(key == "filth") && !(key == "stray")) { return key == "drone"; } return true; } private void ProcessPendingWaveIfReady() { if (!NeedsWaveDirector()) { if (_pendingWave.Count > 0) { _pendingWave.Clear(); _pendingWaveDeadline = -1f; } } else { if (_pendingWave.Count == 0 || Time.time < _pendingWaveDeadline) { return; } float num = Mathf.Max(0f, 1f); if (Time.time - _sceneLoadedTime < num) { _pendingWaveDeadline = Time.time + 0.25f; return; } List list = new List(); for (int i = 0; i < _pendingWave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = _pendingWave[i]; if (pendingWaveEnemy != null && pendingWaveEnemy.Profile.Allowed && (!((Object)(object)pendingWaveEnemy.Source != (Object)null) || !((Object)(object)pendingWaveEnemy.Source.GetComponent() != (Object)null))) { list.Add(pendingWaveEnemy); } } _pendingWave.Clear(); _pendingWaveDeadline = -1f; if (list.Count != 0) { EnsureGlobalPoolScanned(); HashSet replacedIds = new HashSet(); int nonFilthCount = CountNonFilthEnemies(list); int num2 = TryApplyStrongerEnemies(list, replacedIds, nonFilthCount); int num3 = TryApplySafeRandomizer(list, replacedIds, nonFilthCount); if (_debugLogging.Value && (num2 > 0 || num3 > 0)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM wave: natural=" + list.Count + ", stronger=" + num2 + ", random=" + num3 + ".")); } } } } private Dictionary GetPreferredReplacementPools() { EnsureGlobalPoolScanned(); if (_globalPools.Count > 1) { return _globalPools; } return _scenePools; } private void EnsureGlobalPoolScanned() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (_globalPoolScanned) { return; } _globalPoolScanned = true; Type enemyIdentifierType = GetEnemyIdentifierType(); if (enemyIdentifierType == null) { return; } int num = 0; try { Object[] array = Resources.FindObjectsOfTypeAll(enemyIdentifierType); foreach (Object obj in array) { Component val = (Component)(object)((obj is Component) ? obj : null); if ((Object)(object)val == (Object)null || (Object)(object)val.gameObject == (Object)null) { continue; } GameObject gameObject = val.gameObject; Scene scene = gameObject.scene; if ((((Scene)(ref scene)).IsValid() && (Object)(object)gameObject.GetComponent() != (Object)null) || IsHardBlockedTemplateName(((Object)gameObject).name)) { continue; } EnemyProfile profile = EnemyCatalog.GetProfile(ReadEnemyTypeName(val, gameObject), ((Object)gameObject).name, allowUnknown: false, allowExperimentalLarge: true); if (profile.Allowed && !(profile.Key == "maurice")) { int count = _globalPools.Count; RegisterTemplate(_globalPools, profile, gameObject, allowInactiveTemplates: true); if (_globalPools.Count > count) { num++; } } } } catch (Exception ex) { if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM global enemy template scan skipped safely: " + ex.Message)); } } if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM protected global pool contains " + _globalPools.Count + " enemy types after scan.")); } } private int TryApplyStrongerEnemies(List wave, HashSet replacedIds, int nonFilthCount) { if (!_strongerEnabled.Value) { return 0; } if (EcmSpawnedMarker.ActiveCount >= 96) { return 0; } float num = Mathf.Clamp01(_strongerChancePercent.Value / 100f); int num2 = Mathf.Max(0, _strongerMaxReplacementsPerWave.Value); if (num <= 0f || num2 <= 0) { return 0; } int num3 = Mathf.Max(1, _strongerMinimumTierIncrease.Value); int maxIncrease = Mathf.Max(num3, _strongerMaximumTierIncrease.Value); Dictionary preferredReplacementPools = GetPreferredReplacementPools(); if (preferredReplacementPools.Count <= 1) { return 0; } List list = CopyWave(wave); Shuffle(list); int num4 = 0; for (int i = 0; i < list.Count; i++) { if (num4 >= num2) { break; } PendingWaveEnemy pendingWaveEnemy = list[i]; if (pendingWaveEnemy != null && !replacedIds.Contains(pendingWaveEnemy.SourceId) && !(Random.value > num) && IsEntrySourceAlive(pendingWaveEnemy) && !IsMaurice(pendingWaveEnemy.Profile) && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source)) { EnemySpawnCandidate enemySpawnCandidate = ChooseStrongerCandidate(pendingWaveEnemy.Profile, pendingWaveEnemy.Source, preferredReplacementPools, nonFilthCount, num3, maxIncrease); if (enemySpawnCandidate != null && SpawnReplacement(pendingWaveEnemy, enemySpawnCandidate, keepOriginalIfFails: true, "Stronger")) { replacedIds.Add(pendingWaveEnemy.SourceId); num4++; } } } if (num4 > 0) { SpawnAntiSoftlockFilth(wave, _strongerAntiSoftlockFilthCount.Value, "Stronger"); } return num4; } private EnemySpawnCandidate ChooseStrongerCandidate(EnemyProfile sourceProfile, GameObject source, Dictionary pools, int nonFilthCount, int minIncrease, int maxIncrease) { int tier = sourceProfile.Tier; if (tier <= 0) { return null; } int num = Random.Range(minIncrease, maxIncrease + 1); int preferredTier = tier + num; int minTier = tier + minIncrease; int maxTier = tier + maxIncrease; List list = new List(); BuildStrongerCandidateList(list, sourceProfile, source, pools, nonFilthCount, preferredTier, minTier, maxTier, strictPreferredBand: true); if (list.Count == 0) { BuildStrongerCandidateList(list, sourceProfile, source, pools, nonFilthCount, preferredTier, minTier, int.MaxValue, strictPreferredBand: false); } return PickWeightedCandidate(list); } private void BuildStrongerCandidateList(List weighted, EnemyProfile sourceProfile, GameObject source, Dictionary pools, int nonFilthCount, int preferredTier, int minTier, int maxTier, bool strictPreferredBand) { foreach (EnemyTemplatePool value in pools.Values) { if (value == null || !value.HasUsableTemplate || value.Key == sourceProfile.Key || value.Key == "maurice") { continue; } EnemyProfile profileByKey = EnemyCatalog.GetProfileByKey(value.Key); if (profileByKey.Allowed && profileByKey.Tier > sourceProfile.Tier && profileByKey.Tier >= minTier && profileByKey.Tier <= maxTier && (!value.IsLarge || CanUseLargeEnemyCandidate(value, sourceProfile, source, nonFilthCount))) { float num = Mathf.Abs(profileByKey.Tier - preferredTier); float num2 = 1f / (1f + num * 2f); if (!strictPreferredBand && profileByKey.Tier > maxTier) { num2 *= 0.35f; } if (value.IsLarge) { num2 *= 0.5f; } weighted.Add(new WeightedCandidate(new EnemySpawnCandidate(value), num2)); } } } private int TryApplySafeRandomizer(List wave, HashSet replacedIds, int nonFilthCount) { if (!_randomizerEnabled.Value) { return 0; } if (EcmSpawnedMarker.ActiveCount >= 96) { return 0; } float num = Mathf.Clamp01(_randomizerChancePercent.Value / 100f); int num2 = Mathf.Max(0, _maxRandomizerReplacementsPerWave.Value); if (num <= 0f || num2 <= 0) { return 0; } List list = CopyWave(wave); Shuffle(list); int num3 = 0; for (int i = 0; i < list.Count; i++) { if (num3 >= num2) { break; } PendingWaveEnemy pendingWaveEnemy = list[i]; if (pendingWaveEnemy != null && !replacedIds.Contains(pendingWaveEnemy.SourceId) && !(Random.value > num) && IsEntrySourceAlive(pendingWaveEnemy) && !IsMaurice(pendingWaveEnemy.Profile) && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source)) { EnemySpawnCandidate enemySpawnCandidate = ChooseRandomizerCandidate(pendingWaveEnemy.Profile, pendingWaveEnemy.Source, nonFilthCount); if (enemySpawnCandidate != null && SpawnReplacement(pendingWaveEnemy, enemySpawnCandidate, keepOriginalIfFails: true, "Randomizer")) { replacedIds.Add(pendingWaveEnemy.SourceId); num3++; } } } if (num3 > 0) { SpawnAntiSoftlockFilth(wave, _randomizerAntiSoftlockFilthCount.Value, "Randomizer"); } return num3; } private EnemySpawnCandidate ChooseRandomizerCandidate(EnemyProfile sourceProfile, GameObject source, int nonFilthCount) { Dictionary preferredReplacementPools = GetPreferredReplacementPools(); if (preferredReplacementPools.Count <= 1) { return null; } List list = new List(); foreach (EnemyTemplatePool value in preferredReplacementPools.Values) { if (value != null && value.HasUsableTemplate && !(value.Key == sourceProfile.Key) && !(value.Key == "maurice") && (!value.IsLarge || CanUseLargeEnemyCandidate(value, sourceProfile, source, nonFilthCount))) { float num = 1f; if (value.Tier <= sourceProfile.Tier) { num *= 0.75f; } if (value.Tier >= sourceProfile.Tier + 2) { num *= 1.15f; } if (value.IsLarge) { num *= 0.5f; } list.Add(new WeightedCandidate(new EnemySpawnCandidate(value), num)); } } return PickWeightedCandidate(list); } private int SpawnAntiSoftlockFilth(List wave, int requestedCount, string sourceName) { if (requestedCount <= 0 || wave == null || wave.Count < 3) { return 0; } if (EcmSpawnedMarker.ActiveCount >= 96) { return 0; } GameObject val = FindAntiSoftlockAnchor(wave); if ((Object)(object)val == (Object)null) { return 0; } GameObject val2 = FindFilthTemplate(wave); if ((Object)(object)val2 == (Object)null || !IsTemplateSafeToSpawn(val2, "filth")) { return 0; } EnemyProfile profileByKey = EnemyCatalog.GetProfileByKey("filth"); int num = 0; for (int i = 0; i < requestedCount; i++) { if (EcmSpawnedMarker.ActiveCount >= 96) { break; } if (SpawnAdditionalTemplate(val2, val, profileByKey, sourceName + " Anti-Softlock Filth", i)) { num++; } } if (_debugLogging.Value && num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM " + sourceName + " spawned anti-softlock Filth x" + num + ".")); } return num; } private GameObject FindAntiSoftlockAnchor(List wave) { for (int i = 0; i < wave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = wave[i]; if (pendingWaveEnemy != null && (Object)(object)pendingWaveEnemy.Source != (Object)null && pendingWaveEnemy.Source.activeInHierarchy) { return pendingWaveEnemy.Source; } } for (int j = 0; j < wave.Count; j++) { PendingWaveEnemy pendingWaveEnemy2 = wave[j]; if (pendingWaveEnemy2 != null && (Object)(object)pendingWaveEnemy2.Source != (Object)null) { return pendingWaveEnemy2.Source; } } return null; } private GameObject FindFilthTemplate(List wave) { for (int i = 0; i < wave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = wave[i]; if (pendingWaveEnemy != null && pendingWaveEnemy.Profile.Key == "filth" && (Object)(object)pendingWaveEnemy.Source != (Object)null && pendingWaveEnemy.Source.activeInHierarchy && IsTemplateSafeToSpawn(pendingWaveEnemy.Source, "filth")) { return pendingWaveEnemy.Source; } } if (_scenePools.TryGetValue("filth", out var value) && value != null && value.HasUsableTemplate) { return value.GetRandomLiveTemplate(); } if (_globalPools.TryGetValue("filth", out value) && value != null && value.HasUsableTemplate) { return value.GetRandomLiveTemplate(); } return null; } private bool SpawnAdditionalTemplate(GameObject template, GameObject anchor, EnemyProfile profile, string sourceName, int index) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)template == (Object)null || (Object)(object)anchor == (Object)null) { return false; } try { Vector3 val = FindNearbySpawnPosition(anchor.transform.position, index + 100); GameObject val2 = Object.Instantiate(template, val, anchor.transform.rotation, anchor.transform.parent); ((Object)val2).name = ((Object)template).name + " [ECM " + sourceName + "]"; val2.AddComponent(); SanitizeClonedEnemyRuntimeState(val2); if (!val2.activeSelf) { val2.SetActive(true); } if (!IsSpawnedCloneValid(val2, profile.Key)) { Object.Destroy((Object)(object)val2); return false; } ApplyConfiguredEnemyEffects(val2, isModdedEnemy: true); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM skipped anti-softlock Filth safely: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private EnemySpawnCandidate PickWeightedCandidate(List weighted) { if (weighted == null || weighted.Count == 0) { return null; } float num = 0f; for (int i = 0; i < weighted.Count; i++) { num += Mathf.Max(0f, weighted[i].Weight); } if (num <= 0f) { return null; } float num2 = Random.value * num; for (int j = 0; j < weighted.Count; j++) { num2 -= weighted[j].Weight; if (num2 <= 0f) { return weighted[j].Candidate; } } return weighted[weighted.Count - 1].Candidate; } private bool CanUseLargeEnemyCandidate(EnemyTemplatePool pool, EnemyProfile sourceProfile, GameObject source, int nonFilthCount) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (pool == null || !pool.IsLarge) { return true; } if (sourceProfile.IsFlying) { return false; } if (nonFilthCount < 5) { return false; } if ((Object)(object)source == (Object)null) { return false; } if (!HasGroundBelow(source.transform.position)) { return false; } return true; } private bool HasGroundBelow(Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) float num = 10f; RaycastHit val = default(RaycastHit); return Physics.Raycast(position + Vector3.up * 1.25f, Vector3.down, ref val, num + 1.25f, -1, (QueryTriggerInteraction)1); } private int TryApplyImmediateMitosis(GameObject source, EnemyProfile profile) { if (!_mitosisEnabled.Value) { return 0; } if ((Object)(object)source == (Object)null || !source.activeInHierarchy) { return 0; } if (EcmSpawnedMarker.ActiveCount >= 96) { return 0; } if (ShouldAvoidCloningOrReplacingSource(source)) { return 0; } float num = Mathf.Max(1f, _mitosisMultiplier.Value) - 1f; int num2 = Mathf.FloorToInt(num); if (Random.value < num - (float)num2) { num2++; } num2 = Mathf.Max(0, num2); if (num2 <= 0) { return 0; } int num3 = 0; for (int i = 0; i < num2; i++) { if (EcmSpawnedMarker.ActiveCount >= 96) { break; } if (SpawnDuplicate(source, profile, i)) { num3++; } } if (_debugLogging.Value && num3 > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM Mitosis duplicated " + profile.Key + " x" + num3 + ".")); } return num3; } private bool SpawnReplacement(PendingWaveEnemy entry, EnemySpawnCandidate candidate, bool keepOriginalIfFails, string sourceName) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (entry == null || candidate == null || candidate.Pool == null) { return false; } GameObject source = entry.Source; GameObject randomLiveTemplate = candidate.Pool.GetRandomLiveTemplate(); if ((Object)(object)source == (Object)null || (Object)(object)randomLiveTemplate == (Object)null) { return false; } if (!IsTemplateSafeToSpawn(randomLiveTemplate, candidate.Pool.Key)) { return false; } try { GameObject val = Object.Instantiate(randomLiveTemplate, source.transform.position, source.transform.rotation, source.transform.parent); ((Object)val).name = ((Object)randomLiveTemplate).name + " [ECM " + sourceName + "]"; val.AddComponent(); SanitizeClonedEnemyRuntimeState(val); if (!val.activeSelf) { val.SetActive(true); } if (!IsSpawnedCloneValid(val, candidate.Pool.Key)) { Object.Destroy((Object)(object)val); return false; } ApplyConfiguredEnemyEffects(val, isModdedEnemy: true); if (keepOriginalIfFails) { ((MonoBehaviour)this).StartCoroutine(DestroyOriginalAfterReplacementValid(source, val, candidate.Pool.Key)); } else { Object.Destroy((Object)(object)source); } if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM " + sourceName + " replaced " + entry.Profile.Key + " with " + candidate.Pool.Key + ".")); } return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM skipped replacement safely: " + ex.GetType().Name + ": " + ex.Message)); return false; } } [IteratorStateMachine(typeof(d__108))] private IEnumerator DestroyOriginalAfterReplacementValid(GameObject source, GameObject clone, string key) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__108(0) { <>4__this = this, source = source, clone = clone, key = key }; } private bool SpawnDuplicate(GameObject source, EnemyProfile profile, int index) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return false; } try { Vector3 val = FindNearbySpawnPosition(source.transform.position, index); GameObject val2 = Object.Instantiate(source, val, source.transform.rotation, source.transform.parent); ((Object)val2).name = ((Object)source).name + " [ECM Mitosis]"; val2.AddComponent(); SanitizeClonedEnemyRuntimeState(val2); if (!val2.activeSelf) { val2.SetActive(true); } if (!IsSpawnedCloneValid(val2, profile.Key)) { Object.Destroy((Object)(object)val2); return false; } ApplyConfiguredEnemyEffects(val2, isModdedEnemy: true); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM skipped duplicate safely: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private Vector3 FindNearbySpawnPosition(Vector3 origin, int index) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(0.25f, _spawnDistanceMin.Value); float num2 = Mathf.Max(num, _spawnDistanceMax.Value); float num3 = Random.Range(0f, MathF.PI * 2f) + (float)index * 2.399963f; float num4 = Random.Range(num, num2); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(Mathf.Cos(num3) * num4, 0f, Mathf.Sin(num3) * num4); Vector3 val2 = origin + val; RaycastHit val3 = default(RaycastHit); if (Physics.Raycast(val2 + Vector3.up * 4f, Vector3.down, ref val3, 12f, -1, (QueryTriggerInteraction)1)) { val2 = ((RaycastHit)(ref val3)).point + Vector3.up * 0.1f; } return val2; } private bool ApplyConfiguredEnemyEffects(GameObject enemy, bool isModdedEnemy) { if ((Object)(object)enemy == (Object)null || !NeedsEnemyEffectModifiers()) { return false; } bool result = false; if (_sandifyEnabled.Value && Random.value < Mathf.Clamp01(_sandifyChancePercent.Value / 100f)) { ApplySandify(enemy); result = true; } if (_radianceEnabled.Value && Random.value < Mathf.Clamp01(_radianceChancePercent.Value / 100f)) { ApplyRadiant(enemy); result = true; } if (_tankifyEnabled.Value && Random.value < Mathf.Clamp01(_tankifyChancePercent.Value / 100f)) { ApplyTankify(enemy); result = true; } if (_enrageEnabled.Value && Random.value < Mathf.Clamp01(_enrageChancePercent.Value / 100f)) { ApplyEnrage(enemy); result = true; } return result; } private void ApplySandify(GameObject enemy) { Component val = FindEnemyIdentifierComponent(enemy); if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); SetBoolMember(type, val, "sandified", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); InvokeMethodIfExists(type, val, "UpdateBuffs", null); InvokeMethodIfExists(type, val, "UpdateModifiers", null); } } private void ApplyTankify(GameObject enemy) { Component val = FindEnemyIdentifierComponent(enemy); if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); float num = Mathf.Clamp(_tankifyHealthMultiplier.Value, 0.05f, 20f); if (!ReadBoolMember(type, val, "healthBuff", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { InvokeMethodIfExists(type, val, "HealthBuff", new object[1] { num }); } else { float num2 = ReadFloatMember(type, val, "healthBuffModifier", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 1f); SetFloatMember(type, val, "healthBuffModifier", Mathf.Max(0.05f, num2 * num), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } InvokeMethodIfExists(type, val, "ForceGetHealth", null); InvokeMethodIfExists(type, val, "UpdateBuffs", null); InvokeMethodIfExists(type, val, "UpdateModifiers", null); } } private void ApplyRadiant(GameObject enemy) { Component val = FindEnemyIdentifierComponent(enemy); if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); float value = Mathf.Clamp(_radianceTier.Value, 0.05f, 10f); bool value2 = _radianceAffectsDamage.Value; RadiancePreset radiancePreset = GetRadiancePreset(enemy, val); float value3 = radiancePreset.Speed; float value4 = radiancePreset.Health; float num = radiancePreset.Damage; if (_radianceUseCustomMultipliers.Value) { value3 = Mathf.Clamp(_radianceSpeedMultiplier.Value, 0.05f, 20f); value4 = Mathf.Clamp(_radianceHealthMultiplier.Value, 0.05f, 20f); num = Mathf.Clamp(_radianceDamageMultiplier.Value, 0.05f, 20f); } EnableCheatsIfPossible(); SetFloatMember(type, val, "radianceTier", value, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(type, val, "speedBuffModifier", value3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(type, val, "healthBuffModifier", value4, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(type, val, "damageBuffModifier", value2 ? num : 1f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetBoolMember(type, val, "speedBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetBoolMember(type, val, "healthBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetBoolMember(type, val, "damageBuff", value2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); InvokeMethodIfExists(type, val, "BuffAll", null); SetFloatMember(type, val, "radianceTier", value, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(type, val, "speedBuffModifier", value3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(type, val, "healthBuffModifier", value4, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(type, val, "damageBuffModifier", value2 ? num : 1f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetBoolMember(type, val, "speedBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetBoolMember(type, val, "healthBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetBoolMember(type, val, "damageBuff", value2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); InvokeMethodIfExists(type, val, "UpdateBuffs", null); InvokeMethodIfExists(type, val, "UpdateModifiers", null); InvokeMethodIfExists(type, val, "ForceGetHealth", null); } } private RadiancePreset GetRadiancePreset(GameObject enemy, Component enemyIdentifier) { return RadiancePreset.ForEnemyKey(EnemyCatalog.GetProfile(ReadEnemyTypeName(enemyIdentifier, enemy), ((Object)(object)enemy != (Object)null) ? ((Object)enemy).name : string.Empty, allowUnknown: true, allowExperimentalLarge: true).Key); } private void EnableCheatsIfPossible() { try { Type type = AccessTools.TypeByName("AssistController"); Component val = FindSingleComponent(type); if ((Object)(object)val != (Object)null) { SetBoolMember(type, val, "cheatsEnabled", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } catch { } } private void ApplyEnrage(GameObject enemy) { Component[] componentsInChildren = enemy.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); if (CanEnrage(type) && !ReadBoolMember(type, val, "isEnraged", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { InvokeMethodIfExists(type, val, "Enrage", null); } } } } private bool CanEnrage(Type type) { if (type == null || GetCachedMethod(type, "Enrage") == null) { return false; } if (type.Name.IndexOf("enrage", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } Type[] interfaces = type.GetInterfaces(); for (int i = 0; i < interfaces.Length; i++) { string name = interfaces[i].Name; if (name.IndexOf("IEnrage", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Enrage", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private void UpdateUltraHot() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if (!CanModifyCurrentScene() || _ultrahotEnabled == null || !_ultrahotEnabled.Value) { if (_ultrahotWasActive) { Time.timeScale = 1f; _ultrahotWasActive = false; } return; } try { if ((Object)(object)_cachedMovement == (Object)null || (Object)(object)_cachedMovementRigidbody == (Object)null) { _cachedMovement = FindSingleComponent(GetNewMovementType()); if ((Object)(object)_cachedMovement == (Object)null) { return; } _cachedMovementRigidbody = ReadRigidbodyMember(((object)_cachedMovement).GetType(), _cachedMovement, "rb"); } Rigidbody cachedMovementRigidbody = _cachedMovementRigidbody; if ((Object)(object)cachedMovementRigidbody == (Object)null) { _cachedMovement = null; _cachedMovementRigidbody = null; return; } float num = Mathf.Max(0.1f, _ultrahotVelocityForNormalTime.Value); Vector3 velocity = cachedMovementRigidbody.velocity; float num2 = ((Vector3)(ref velocity)).magnitude / num; num2 = Mathf.Clamp(num2, Mathf.Max(0.001f, _ultrahotMinimumTimeScale.Value), Mathf.Max(_ultrahotMinimumTimeScale.Value, _ultrahotMaximumTimeScale.Value)); Time.timeScale = Mathf.Lerp(Time.timeScale, num2, Time.unscaledDeltaTime * Mathf.Max(0.1f, _ultrahotSmoothing.Value)); _ultrahotWasActive = true; } catch (Exception ex) { if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("ULTRAHOT update skipped safely: " + ex.Message)); } } } private void UpdateDualWieldMaintenance() { if (!CanModifyCurrentScene() || _dualWieldEnabled == null || !_dualWieldEnabled.Value || Time.time < _nextDualWieldRefresh) { return; } _nextDualWieldRefresh = Time.time + Mathf.Clamp(_dualWieldRefreshSeconds.Value, 0.05f, 10f); try { Type gunControlType = GetGunControlType(); Type dualWieldType = GetDualWieldType(); if (gunControlType == null || dualWieldType == null) { return; } Component val = FindSingleComponent(gunControlType); if (!((Object)(object)val == (Object)null)) { Component[] componentsInChildren = val.GetComponentsInChildren(dualWieldType, true); int num = Mathf.Clamp(_dualWieldStacks.Value, 0, 16); for (int i = ((componentsInChildren != null) ? componentsInChildren.Length : 0); i < num; i++) { AddDualWieldStack(val, dualWieldType, i); } } } catch (Exception ex) { if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Dual Wield maintenance skipped safely: " + ex.Message)); } } } private void AddDualWieldStack(Component gunControl, Type dualType, int existingCount) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("ECM Dual Wield"); val.transform.SetParent(gunControl.transform, true); val.transform.localRotation = Quaternion.identity; val.transform.localScale = (Vector3)((existingCount % 2 == 0) ? new Vector3(-1f, 1f, 1f) : Vector3.one); if (existingCount == 0) { val.transform.localPosition = Vector3.zero; } else if (existingCount % 2 == 0) { val.transform.localPosition = new Vector3((float)(existingCount / 2) * -1.5f, 0f, 0f); } else { val.transform.localPosition = new Vector3((float)((existingCount + 1) / 2) * 1.5f, 0f, 0f); } Component instance = val.AddComponent(dualType); SetFloatMember(dualType, instance, "delay", 0.05f + (float)existingCount / 20f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SetFloatMember(dualType, instance, "juiceAmount", Mathf.Max(1f, _dualWieldDuration.Value), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } private void RegisterTemplate(Dictionary pools, EnemyProfile profile, GameObject source, bool allowInactiveTemplates) { if (pools != null && !((Object)(object)source == (Object)null) && profile.Allowed && IsTemplateSafeToSpawn(source, profile.Key)) { if (!pools.TryGetValue(profile.Key, out var value)) { value = new EnemyTemplatePool(profile.Key, profile.Tier, profile.IsLarge, profile.IsFlying, allowInactiveTemplates); pools[profile.Key] = value; } value.AddTemplate(source); } } private bool IsTemplateSafeToSpawn(GameObject template, string key) { if ((Object)(object)template == (Object)null) { return false; } if ((Object)(object)template.GetComponent() != (Object)null) { return false; } if (IsHardBlockedTemplateName((key ?? string.Empty) + " " + ((Object)template).name)) { return false; } if (!HasEnemyIdentifierComponent(template)) { return false; } return true; } private bool IsSpawnedCloneValid(GameObject clone, string key) { if ((Object)(object)clone == (Object)null || !clone.activeInHierarchy) { return false; } if (IsHardBlockedTemplateName((key ?? string.Empty) + " " + ((Object)clone).name)) { return false; } if (!HasEnemyIdentifierComponent(clone)) { return false; } return true; } private bool ShouldAvoidCloningOrReplacingSource(GameObject source) { if ((Object)(object)source == (Object)null) { return true; } try { List list = FindEnemyIdentifierComponents(source); for (int i = 0; i < list.Count; i++) { Component val = list[i]; if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); if (ReadBoolMember(type, val, "blessed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM skipped Idol-blessed source: " + ((Object)source).name)); } return true; } } } catch { } return false; } private void SanitizeClonedEnemyRuntimeState(GameObject clone) { if ((Object)(object)clone == (Object)null) { return; } List list = FindEnemyIdentifierComponents(clone); for (int i = 0; i < list.Count; i++) { Component val = list[i]; if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); SetBoolMember(type, val, "blessed", value: false, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); InvokeMethodIfExists(type, val, "UpdateBuffs", null); InvokeMethodIfExists(type, val, "UpdateModifiers", null); } } } private bool IsEntrySourceAlive(PendingWaveEnemy entry) { if (entry != null && (Object)(object)entry.Source != (Object)null) { return entry.Source.activeInHierarchy; } return false; } private int CountNonFilthEnemies(List wave) { int num = 0; for (int i = 0; i < wave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = wave[i]; if (pendingWaveEnemy != null && pendingWaveEnemy.Profile.Key != "filth") { num++; } } return num; } private List CopyWave(List wave) { List list = new List(); if (wave == null) { return list; } for (int i = 0; i < wave.Count; i++) { if (wave[i] != null) { list.Add(wave[i]); } } return list; } private void Shuffle(List list) { for (int i = 0; i < list.Count; i++) { int index = Random.Range(i, list.Count); T value = list[i]; list[i] = list[index]; list[index] = value; } } private bool IsMaurice(EnemyProfile profile) { if (!(profile.Key == "maurice")) { return Normalize(profile.Key).Contains("maliciousface"); } return true; } private bool IsHardBlockedTemplateName(string name) { string text = Normalize(name); if (!text.Contains("verycancerous") && !text.Contains("cancerousrodent") && !text.Contains("rodent") && !text.Contains("idol") && !text.Contains("deathcatcher") && !text.Contains("bigjohn") && !text.Contains("jakito") && !text.Contains("somethingwicked") && !text.Contains("prime") && !text.Contains("gabriel") && !text.Contains("fleshprison") && !text.Contains("fleshpanopticon") && !text.Contains("leviathan") && !text.Contains("earthmover")) { return text.Contains("v2"); } return true; } private Type GetEnemyIdentifierType() { if (_enemyIdentifierType == null) { _enemyIdentifierType = AccessTools.TypeByName("EnemyIdentifier"); } return _enemyIdentifierType; } private Type GetGunControlType() { if (_gunControlType == null) { _gunControlType = AccessTools.TypeByName("GunControl"); } return _gunControlType; } private Type GetDualWieldType() { if (_dualWieldType == null) { _dualWieldType = AccessTools.TypeByName("DualWield"); } return _dualWieldType; } private Type GetNewMovementType() { if (_newMovementType == null) { _newMovementType = AccessTools.TypeByName("NewMovement"); } return _newMovementType; } private Component FindSingleComponent(Type type) { if (type == null) { return null; } Object obj = Object.FindObjectOfType(type); return (Component)(object)((obj is Component) ? obj : null); } private bool HasEnemyIdentifierComponent(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return false; } Type enemyIdentifierType = GetEnemyIdentifierType(); if (enemyIdentifierType == null) { return false; } if (!((Object)(object)gameObject.GetComponent(enemyIdentifierType) != (Object)null)) { return (Object)(object)gameObject.GetComponentInChildren(enemyIdentifierType, true) != (Object)null; } return true; } private Component FindEnemyIdentifierComponent(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return null; } Type enemyIdentifierType = GetEnemyIdentifierType(); if (enemyIdentifierType == null) { return null; } return gameObject.GetComponent(enemyIdentifierType) ?? gameObject.GetComponentInChildren(enemyIdentifierType, true); } private List FindEnemyIdentifierComponents(GameObject gameObject) { List list = new List(); if ((Object)(object)gameObject == (Object)null) { return list; } Type enemyIdentifierType = GetEnemyIdentifierType(); if (enemyIdentifierType == null) { return list; } Component component = gameObject.GetComponent(enemyIdentifierType); if ((Object)(object)component != (Object)null) { list.Add(component); } Component[] componentsInChildren = gameObject.GetComponentsInChildren(enemyIdentifierType, true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null && !list.Contains(componentsInChildren[i])) { list.Add(componentsInChildren[i]); } } return list; } private string ReadEnemyTypeName(object enemyObject, GameObject source) { if ((Object)(object)source != (Object)null) { string text = NormalizeObjectName(((Object)source).name); if (!string.IsNullOrEmpty(text)) { return text; } } if (enemyObject != null) { Type type = enemyObject.GetType(); string[] array = new string[6] { "enemyType", "EnemyType", "type", "enemyName", "FullName", "fullName" }; for (int i = 0; i < array.Length; i++) { string text2 = ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, array[i]); if (!string.IsNullOrEmpty(text2)) { return text2; } } } if (!((Object)(object)source != (Object)null)) { return string.Empty; } return ((Object)source).name; } private string NormalizeObjectName(string name) { string text = Normalize(name); if (text.Contains("maliciousface") || text.Contains("maurice")) { return "maurice"; } if (text.Contains("hideousmass")) { return "hideousmass"; } if (text.Contains("sisyph") || text.Contains("insurrection")) { return "insurrectionist"; } if (text.Contains("streetclean")) { return "streetcleaner"; } if (text.Contains("guttertank")) { return "guttertank"; } if (text.Contains("gutterman")) { return "gutterman"; } if (text.Contains("mindflayer")) { return "mindflayer"; } if (text.Contains("swordsmachine")) { return "swordsmachine"; } if (text.Contains("cerberus")) { return "cerberus"; } if (text.Contains("ferryman")) { return "ferryman"; } if (text.Contains("providence")) { return "providence"; } if (text.Contains("sentry")) { return "sentry"; } if (text.Contains("virtue")) { return "virtue"; } if (text.Contains("drone")) { return "drone"; } if (text.Contains("soldier")) { return "soldier"; } if (text.Contains("schism")) { return "schism"; } if (text.Contains("stalker")) { return "stalker"; } if (text.Contains("mannequin")) { return "mannequin"; } if (text.Contains("stray")) { return "stray"; } if (text.Contains("filth") || text.Contains("zombie")) { return "filth"; } if (text.Contains("minotaur")) { return "minotaur"; } if (text.Contains("mirrorreaper") || text.Contains("mirror")) { return "mirrorreaper"; } if (text == "power" || text.Contains("enemytypepower")) { return "power"; } return string.Empty; } private static string ReflectionCacheKey(Type type, BindingFlags flags, string memberName) { string[] obj = new string[5] { (type != null) ? type.FullName : string.Empty, "|", null, null, null }; int num = (int)flags; obj[2] = num.ToString(); obj[3] = "|"; obj[4] = memberName; return string.Concat(obj); } private FieldInfo GetCachedField(Type type, string memberName, BindingFlags flags) { if (type == null || string.IsNullOrEmpty(memberName)) { return null; } string key = ReflectionCacheKey(type, flags, memberName); if (_fieldCache.TryGetValue(key, out var value)) { return value; } value = type.GetField(memberName, flags); _fieldCache[key] = value; return value; } private PropertyInfo GetCachedProperty(Type type, string memberName, BindingFlags flags) { if (type == null || string.IsNullOrEmpty(memberName)) { return null; } string key = ReflectionCacheKey(type, flags, memberName); if (_propertyCache.TryGetValue(key, out var value)) { return value; } value = type.GetProperty(memberName, flags); _propertyCache[key] = value; return value; } private MethodInfo GetCachedMethod(Type type, string methodName) { if (type == null || string.IsNullOrEmpty(methodName)) { return null; } string key = (type.FullName ?? type.Name) + "|method|" + methodName; if (_methodCache.TryGetValue(key, out var value)) { return value; } value = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); _methodCache[key] = value; return value; } private string ReadStringMember(object instance, Type type, BindingFlags flags, string memberName) { try { FieldInfo cachedField = GetCachedField(type, memberName, flags); if (cachedField != null) { object value = cachedField.GetValue(instance); if (value != null) { return value.ToString(); } } PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags); if (cachedProperty != null && cachedProperty.CanRead && cachedProperty.GetIndexParameters().Length == 0) { object value2 = cachedProperty.GetValue(instance, null); if (value2 != null) { return value2.ToString(); } } } catch { } return null; } private bool ReadBoolMember(Type type, object instance, string memberName, BindingFlags flags) { try { FieldInfo cachedField = GetCachedField(type, memberName, flags); if (cachedField != null && cachedField.FieldType == typeof(bool)) { return (bool)cachedField.GetValue(instance); } PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags); if (cachedProperty != null && cachedProperty.PropertyType == typeof(bool) && cachedProperty.CanRead) { return (bool)cachedProperty.GetValue(instance, null); } } catch { } return false; } private float ReadFloatMember(Type type, object instance, string memberName, BindingFlags flags, float fallback) { try { FieldInfo cachedField = GetCachedField(type, memberName, flags); if (cachedField != null && cachedField.FieldType == typeof(float)) { return (float)cachedField.GetValue(instance); } PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags); if (cachedProperty != null && cachedProperty.PropertyType == typeof(float) && cachedProperty.CanRead) { return (float)cachedProperty.GetValue(instance, null); } } catch { } return fallback; } private Rigidbody ReadRigidbodyMember(Type type, object instance, string memberName) { try { FieldInfo cachedField = GetCachedField(type, memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (cachedField != null) { object? value = cachedField.GetValue(instance); return (Rigidbody)((value is Rigidbody) ? value : null); } PropertyInfo cachedProperty = GetCachedProperty(type, memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (cachedProperty != null && cachedProperty.CanRead) { object? value2 = cachedProperty.GetValue(instance, null); return (Rigidbody)((value2 is Rigidbody) ? value2 : null); } } catch { } return null; } private void SetBoolMember(Type type, object instance, string memberName, bool value, BindingFlags flags) { try { FieldInfo cachedField = GetCachedField(type, memberName, flags); if (cachedField != null && cachedField.FieldType == typeof(bool)) { cachedField.SetValue(instance, value); return; } PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags); if (cachedProperty != null && cachedProperty.PropertyType == typeof(bool) && cachedProperty.CanWrite) { cachedProperty.SetValue(instance, value, null); } } catch { } } private void SetFloatMember(Type type, object instance, string memberName, float value, BindingFlags flags) { try { FieldInfo cachedField = GetCachedField(type, memberName, flags); if (cachedField != null && cachedField.FieldType == typeof(float)) { cachedField.SetValue(instance, value); return; } PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags); if (cachedProperty != null && cachedProperty.PropertyType == typeof(float) && cachedProperty.CanWrite) { cachedProperty.SetValue(instance, value, null); } } catch { } } private void InvokeMethodIfExists(Type type, object instance, string methodName, object[] args) { try { MethodInfo cachedMethod = GetCachedMethod(type, methodName); if (cachedMethod != null) { cachedMethod.Invoke(instance, args); } } catch { } } private void LogBlocked(string enemyType, string objectName, string reason) { if (_debugLogging.Value) { string item = Normalize(enemyType + " " + objectName + " " + reason); if (_loggedBlocked.Add(item)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM skipped enemy. type='" + enemyType + "' object='" + objectName + "' reason='" + reason + "'.")); } } } internal static string Normalize(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } char[] array = new char[value.Length]; int length = 0; for (int i = 0; i < value.Length; i++) { char c = char.ToLowerInvariant(value[i]); if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { array[length++] = c; } } return new string(array, 0, length); } } internal struct RadiancePreset { public readonly float Health; public readonly float Speed; public readonly float Damage; public RadiancePreset(float health, float speed, float damage) { Health = health; Speed = speed; Damage = damage; } public static RadiancePreset ForEnemyKey(string key) { key = EasyCampaignTweaksPlugin.Normalize(key); return key switch { "filth" => new RadiancePreset(5f, 2f, 1.5f), "stray" => new RadiancePreset(2.5f, 2f, 1.5f), "schism" => new RadiancePreset(2.5f, 2f, 1.5f), "soldier" => new RadiancePreset(2f, 2f, 1.5f), "stalker" => new RadiancePreset(2f, 2f, 1.5f), "insurrectionist" => new RadiancePreset(1.5f, 1.5f, 1.5f), "ferryman" => new RadiancePreset(1.5f, 1.5f, 1.5f), "swordsmachine" => new RadiancePreset(1.5f, 1f, 1.5f), "drone" => new RadiancePreset(2.5f, 2f, 1.5f), "streetcleaner" => new RadiancePreset(1.5f, 2f, 1.5f), "mindflayer" => new RadiancePreset(2f, 1.5f, 1.5f), "sentry" => new RadiancePreset(1.5f, 1.5f, 1.5f), "gutterman" => new RadiancePreset(1.5f, 1.5f, 1.5f), "guttertank" => new RadiancePreset(1.5f, 1.5f, 1.5f), "maurice" => new RadiancePreset(2f, 1.5f, 1.5f), "cerberus" => new RadiancePreset(1.5f, 1.35f, 1.5f), "hideousmass" => new RadiancePreset(2f, 2f, 1.5f), "mannequin" => new RadiancePreset(1.5f, 1f, 1.5f), "virtue" => new RadiancePreset(1.5f, 1.5f, 1.5f), _ => new RadiancePreset(1.5f, 1.5f, 1.5f), }; } } internal sealed class PendingWaveEnemy { public readonly GameObject Source; public readonly EnemyProfile Profile; public readonly float TimeStarted; public readonly int SourceId; public PendingWaveEnemy(GameObject source, EnemyProfile profile, float timeStarted) { Source = source; Profile = profile; TimeStarted = timeStarted; SourceId = (((Object)(object)source != (Object)null) ? ((Object)source).GetInstanceID() : 0); } } internal sealed class EnemyTemplatePool { public readonly string Key; public readonly int Tier; public readonly bool IsLarge; public readonly bool IsFlying; private readonly bool _allowInactiveTemplates; private readonly List _templates = new List(); public bool HasUsableTemplate { get { CleanupDeadTemplates(); return _templates.Count > 0; } } public EnemyTemplatePool(string key, int tier, bool isLarge, bool isFlying, bool allowInactiveTemplates) { Key = key; Tier = tier; IsLarge = isLarge; IsFlying = isFlying; _allowInactiveTemplates = allowInactiveTemplates; } public void AddTemplate(GameObject template) { if (!((Object)(object)template == (Object)null) && !_templates.Contains(template)) { _templates.Add(template); CleanupDeadTemplates(); } } public GameObject GetRandomLiveTemplate() { CleanupDeadTemplates(); if (_templates.Count == 0) { return null; } return _templates[Random.Range(0, _templates.Count)]; } private void CleanupDeadTemplates() { for (int num = _templates.Count - 1; num >= 0; num--) { GameObject val = _templates[num]; if ((Object)(object)val == (Object)null || (!_allowInactiveTemplates && !val.activeInHierarchy)) { _templates.RemoveAt(num); } } } } internal sealed class EnemySpawnCandidate { public readonly EnemyTemplatePool Pool; public EnemySpawnCandidate(EnemyTemplatePool pool) { Pool = pool; } } internal sealed class WeightedCandidate { public readonly EnemySpawnCandidate Candidate; public readonly float Weight; public WeightedCandidate(EnemySpawnCandidate candidate, float weight) { Candidate = candidate; Weight = weight; } } internal struct EnemyProfile { public readonly string Key; public readonly int Tier; public readonly bool Allowed; public readonly string BlockReason; public readonly bool IsLarge; public readonly bool IsFlying; public EnemyProfile(string key, int tier, bool allowed, string blockReason, bool isLarge, bool isFlying) { Key = key; Tier = tier; Allowed = allowed; BlockReason = blockReason; IsLarge = isLarge; IsFlying = isFlying; } public static EnemyProfile AllowedProfile(string key, int tier, bool isLarge = false, bool isFlying = false) { return new EnemyProfile(key, tier, allowed: true, string.Empty, isLarge, isFlying); } public static EnemyProfile Blocked(string reason) { return new EnemyProfile(string.Empty, 0, allowed: false, reason, isLarge: false, isFlying: false); } } internal static class EnemyCatalog { public static readonly string[] TierOrder = new string[22] { "filth", "stray", "schism", "soldier", "drone", "streetcleaner", "stalker", "mannequin", "virtue", "sentry", "swordsmachine", "cerberus", "gutterman", "providence", "guttertank", "mindflayer", "power", "ferryman", "hideousmass", "insurrectionist", "minotaur", "mirrorreaper" }; public static EnemyProfile GetProfileByKey(string key) { return GetProfile(key, key, allowUnknown: false, allowExperimentalLarge: true); } public static EnemyProfile GetProfile(string enemyTypeName, string objectName, bool allowUnknown, bool allowExperimentalLarge) { string text = EasyCampaignTweaksPlugin.Normalize((enemyTypeName ?? string.Empty) + " " + (objectName ?? string.Empty)); if (text.Contains("verycancerous") || text.Contains("cancerousrodent") || text.Contains("rodent")) { return EnemyProfile.Blocked("rodent/special"); } if (text.Contains("idol")) { return EnemyProfile.Blocked("idol/special"); } if (text.Contains("deathcatcher")) { return EnemyProfile.Blocked("deathcatcher/special"); } if (text.Contains("bigjohn") || text.Contains("jakito") || text.Contains("somethingwicked")) { return EnemyProfile.Blocked("special"); } if (text.Contains("prime") || text.Contains("gabriel") || text.Contains("v2") || text.Contains("fleshprison") || text.Contains("fleshpanopticon") || text.Contains("leviathan") || text.Contains("earthmover")) { return EnemyProfile.Blocked("boss/special"); } if (text.Contains("puppet")) { return EnemyProfile.Blocked("puppet/unstable"); } if (text == "20" || text.Contains("sentry")) { return EnemyProfile.AllowedProfile("sentry", TierOf("sentry")); } if (text == "19" || text.Contains("sisyph") || text.Contains("insurrection")) { return EnemyProfile.AllowedProfile("insurrectionist", TierOf("insurrectionist"), isLarge: true); } if (text == "2" || text.Contains("hideousmass")) { return EnemyProfile.AllowedProfile("hideousmass", TierOf("hideousmass"), isLarge: true); } if (text == "4" || text.Contains("maliciousface") || text.Contains("maurice")) { return EnemyProfile.AllowedProfile("maurice", 11); } if (text.Contains("guttertank")) { return EnemyProfile.AllowedProfile("guttertank", TierOf("guttertank")); } if (text.Contains("gutterman")) { return EnemyProfile.AllowedProfile("gutterman", TierOf("gutterman")); } if (text.Contains("mindflayer")) { return EnemyProfile.AllowedProfile("mindflayer", TierOf("mindflayer"), isLarge: false, isFlying: true); } if (text.Contains("power")) { return EnemyProfile.AllowedProfile("power", TierOf("power"), isLarge: false, isFlying: true); } if (text.Contains("providence")) { return EnemyProfile.AllowedProfile("providence", TierOf("providence"), isLarge: false, isFlying: true); } if (text.Contains("ferryman")) { return EnemyProfile.AllowedProfile("ferryman", TierOf("ferryman")); } if (text.Contains("cerberus")) { return EnemyProfile.AllowedProfile("cerberus", TierOf("cerberus")); } if (text.Contains("swordsmachine")) { return EnemyProfile.AllowedProfile("swordsmachine", TierOf("swordsmachine")); } if (text.Contains("virtue")) { return EnemyProfile.AllowedProfile("virtue", TierOf("virtue"), isLarge: false, isFlying: true); } if (text.Contains("streetclean")) { return EnemyProfile.AllowedProfile("streetcleaner", TierOf("streetcleaner")); } if (text.Contains("stalker")) { return EnemyProfile.AllowedProfile("stalker", TierOf("stalker")); } if (text.Contains("mannequin")) { return EnemyProfile.AllowedProfile("mannequin", TierOf("mannequin")); } if (text.Contains("drone")) { return EnemyProfile.AllowedProfile("drone", TierOf("drone"), isLarge: false, isFlying: true); } if (text.Contains("soldier")) { return EnemyProfile.AllowedProfile("soldier", TierOf("soldier")); } if (text.Contains("schism")) { return EnemyProfile.AllowedProfile("schism", TierOf("schism")); } if (text.Contains("stray")) { return EnemyProfile.AllowedProfile("stray", TierOf("stray")); } if (text.Contains("filth") || text.Contains("zombie")) { return EnemyProfile.AllowedProfile("filth", TierOf("filth")); } if (allowExperimentalLarge) { if (text.Contains("minotaur")) { return EnemyProfile.AllowedProfile("minotaur", TierOf("minotaur"), isLarge: true); } if (text.Contains("mirrorreaper") || text.Contains("mirror")) { return EnemyProfile.AllowedProfile("mirrorreaper", TierOf("mirrorreaper"), isLarge: true); } } else if (text.Contains("minotaur") || text.Contains("mirrorreaper") || text.Contains("mirror")) { return EnemyProfile.Blocked("experimental large enemy disabled"); } if (allowUnknown) { return EnemyProfile.AllowedProfile(text, 8); } return EnemyProfile.Blocked("unknown enemy type"); } public static int TierOf(string key) { key = EasyCampaignTweaksPlugin.Normalize(key); for (int i = 0; i < TierOrder.Length; i++) { if (TierOrder[i] == key) { return i + 1; } } return 0; } } internal sealed class EcmSpawnedMarker : MonoBehaviour { public const string CloneNameTag = "[ECM"; public static int ActiveCount; private void OnEnable() { ActiveCount++; } private void OnDisable() { ActiveCount = Mathf.Max(0, ActiveCount - 1); } } }