Decompiled source of LevelDeath v0.8.1
LevelDeath.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
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("LevelDeath")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LevelDeath")] [assembly: AssemblyTitle("LevelDeath")] [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 LevelDeath { [BepInPlugin("oai.ultrakill.leveldeath", "Level Death", "0.8.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class LevelDeathPlugin : BaseUnityPlugin { private sealed class PendingWaveEnemy { public readonly GameObject Source; public readonly EnemyProfile Profile; public readonly float TimeStarted; public readonly Vector3 Position; public readonly Quaternion Rotation; public readonly Transform Parent; private readonly int _sourceId; public int SourceId => _sourceId; public PendingWaveEnemy(GameObject source, EnemyProfile profile, float timeStarted) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) Source = source; Profile = profile; TimeStarted = timeStarted; Position = (((Object)(object)source != (Object)null) ? source.transform.position : Vector3.zero); Rotation = (((Object)(object)source != (Object)null) ? source.transform.rotation : Quaternion.identity); Parent = (((Object)(object)source != (Object)null) ? source.transform.parent : null); _sourceId = (((Object)(object)source != (Object)null) ? ((Object)source).GetInstanceID() : 0); } } private sealed class WeightedWaveEnemy { public readonly PendingWaveEnemy Enemy; public readonly float Weight; public WeightedWaveEnemy(PendingWaveEnemy enemy, float weight) { Enemy = enemy; Weight = Mathf.Max(0.001f, weight); } } private sealed class EnemySpawnCandidate { public readonly EnemyTemplatePool Pool; public readonly int EffectiveCost; public readonly bool IsGlobalOnly; public bool IsLarge { get { if (Pool != null) { return Pool.IsLarge; } return false; } } public int BaseCost { get { if (Pool == null) { return EffectiveCost; } return Pool.Cost; } } public EnemySpawnCandidate(EnemyTemplatePool pool, int effectiveCost, bool isGlobalOnly) { Pool = pool; EffectiveCost = Mathf.Max(1, effectiveCost); IsGlobalOnly = isGlobalOnly; } } private sealed class EnemyCooldownState { public float Heat; public float LastUpdatedTime; } private sealed class WeightedCandidate { public readonly EnemySpawnCandidate Candidate; public readonly float Weight; public WeightedCandidate(EnemySpawnCandidate candidate, float weight) { Candidate = candidate; Weight = Mathf.Max(0.001f, weight); } } [CompilerGenerated] private sealed class <DestroyOriginalAfterReplacementValid>d__131 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public GameObject source; public LevelDeathPlugin <>4__this; public GameObject clone; public string key; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DestroyOriginalAfterReplacementValid>d__131(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; LevelDeathPlugin levelDeathPlugin = <>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 (levelDeathPlugin.IsSpawnedCloneValid(clone, key)) { Object.Destroy((Object)(object)source); } else { levelDeathPlugin.DestroyCloneIfPresent(clone); if (levelDeathPlugin._debugLogging != null && levelDeathPlugin._debugLogging.Value) { ((BaseUnityPlugin)levelDeathPlugin).Logger.LogInfo((object)("Level Death kept original enemy because its replacement failed validation after spawn: " + ((Object)source).name)); } } 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(); } } [CompilerGenerated] private sealed class <FindEnemyIdentifierComponents>d__180 : IEnumerable<Component>, IEnumerable, IEnumerator<Component>, IEnumerator, IDisposable { private int <>1__state; private Component <>2__current; private int <>l__initialThreadId; private GameObject gameObject; public GameObject <>3__gameObject; public LevelDeathPlugin <>4__this; private Type <enemyIdentifierType>5__2; private Component <self>5__3; private Component[] <>7__wrap3; private int <>7__wrap4; Component IEnumerator<Component>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FindEnemyIdentifierComponents>d__180(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <enemyIdentifierType>5__2 = null; <self>5__3 = null; <>7__wrap3 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; LevelDeathPlugin levelDeathPlugin = <>4__this; Component[] componentsInChildren; switch (num) { default: return false; case 0: <>1__state = -1; if ((Object)(object)gameObject == (Object)null) { return false; } <enemyIdentifierType>5__2 = levelDeathPlugin.GetEnemyIdentifierType(); if (<enemyIdentifierType>5__2 == null) { return false; } <self>5__3 = gameObject.GetComponent(<enemyIdentifierType>5__2); if ((Object)(object)<self>5__3 != (Object)null) { <>2__current = <self>5__3; <>1__state = 1; return true; } goto IL_0096; case 1: <>1__state = -1; goto IL_0096; case 2: { <>1__state = -1; goto IL_00fa; } IL_0108: if (<>7__wrap4 < <>7__wrap3.Length) { Component val = <>7__wrap3[<>7__wrap4]; if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)<self>5__3) { <>2__current = val; <>1__state = 2; return true; } goto IL_00fa; } <>7__wrap3 = null; return false; IL_0096: componentsInChildren = gameObject.GetComponentsInChildren(<enemyIdentifierType>5__2, true); if (componentsInChildren == null) { return false; } <>7__wrap3 = componentsInChildren; <>7__wrap4 = 0; goto IL_0108; IL_00fa: <>7__wrap4++; goto IL_0108; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<Component> IEnumerable<Component>.GetEnumerator() { <FindEnemyIdentifierComponents>d__180 <FindEnemyIdentifierComponents>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <FindEnemyIdentifierComponents>d__ = this; } else { <FindEnemyIdentifierComponents>d__ = new <FindEnemyIdentifierComponents>d__180(0) { <>4__this = <>4__this }; } <FindEnemyIdentifierComponents>d__.gameObject = <>3__gameObject; return <FindEnemyIdentifierComponents>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<Component>)this).GetEnumerator(); } } [CompilerGenerated] private sealed class <PrimeGlobalEnemyPoolSoon>d__159 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public LevelDeathPlugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <PrimeGlobalEnemyPoolSoon>d__159(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; LevelDeathPlugin levelDeathPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = null; <>1__state = 2; return true; case 2: <>1__state = -1; if (levelDeathPlugin._enabled == null || !levelDeathPlugin._enabled.Value) { return false; } if (levelDeathPlugin._enemyPoolMode == null || levelDeathPlugin._enemyPoolMode.Value != EnemyPoolMode.GlobalEnemies) { return false; } if (levelDeathPlugin._cyberGrindOnly != null && levelDeathPlugin._cyberGrindOnly.Value) { string text = Normalize(levelDeathPlugin._currentSceneName); if (!text.Contains("cyber") && !text.Contains("endless") && !text.Contains("grid")) { return false; } } levelDeathPlugin.ScanGlobalEnemyPoolOncePerScene(); 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 = "oai.ultrakill.leveldeath"; public const string PluginName = "Level Death"; public const string PluginVersion = "0.8.4"; internal static LevelDeathPlugin Instance; internal static ManualLogSource Log; private Harmony _harmony; private Type _enemyIdentifierType; private ConfigEntry<bool> _enabled; private ConfigEntry<float> _maxMeterCarriedBetweenScenes; private ConfigEntry<bool> _showHud; private ConfigEntry<bool> _cyberGrindOnly; private ConfigEntry<StartingLevelMode> _startingLevelMode; private ConfigEntry<float> _startingMeterPercent; private ConfigEntry<EnemyPoolMode> _enemyPoolMode; private ConfigEntry<DirectorActionMode> _directorActionMode; private ConfigEntry<bool> _allowUnknownEnemyTypes; private ConfigEntry<bool> _debugLogging; private ConfigEntry<float> _styleGainScale; private ConfigEntry<float> _softCapGainMultiplier; private ConfigEntry<float> _levelCompletionReward; private ConfigEntry<float> _deathMeterRetainedMultiplier; private ConfigEntry<float> _damagePenaltyFlat; private ConfigEntry<float> _damagePenaltyScale; private ConfigEntry<float> _damagePenaltyMultiplier; private ConfigEntry<float> _isolatedHitPenaltyMultiplier; private ConfigEntry<float> _consecutiveHitWindowSeconds; private ConfigEntry<float> _consecutiveHitPenaltyStep; private ConfigEntry<float> _maxConsecutiveHitPenaltyMultiplier; private ConfigEntry<float> _minimumMeterForBonusSpawns; private ConfigEntry<float> _toughPoolStartsAtMeter; private ConfigEntry<float> _globalEnemyMinimumMeter; private ConfigEntry<float> _spawnChanceMultiplier; private ConfigEntry<int> _maxBonusBudget; private ConfigEntry<int> _maxOvercapBonusBudget; private ConfigEntry<float> _deathMarkBudgetMultiplier; private ConfigEntry<int> _maxActiveBonusEnemies; private ConfigEntry<float> _globalEnemyCostMultiplier; private ConfigEntry<int> _globalEnemyFlatSurcharge; private ConfigEntry<float> _minimumSecondsBetweenBonusSpawns; private ConfigEntry<float> _minimumSceneAgeForSpawns; private ConfigEntry<float> _spawnRadiusMin; private ConfigEntry<float> _spawnRadiusMax; private ConfigEntry<bool> _allowTypeReplacements; private ConfigEntry<float> _replacementChanceLevel2; private ConfigEntry<float> _replacementChanceLevel3; private ConfigEntry<float> _replacementChanceLevel4; private ConfigEntry<float> _waveBatchWindowSeconds; private ConfigEntry<float> _lowTierWaveReadSeconds; private ConfigEntry<int> _processWaveImmediatelyAtSize; private ConfigEntry<bool> _allowSingleFodderReplacement; private ConfigEntry<int> _fullDirectorStartsAtWaveSize; private ConfigEntry<int> _mediumWaveMaxDuplicates; private ConfigEntry<int> _mediumWaveMaxReplacements; private ConfigEntry<int> _maxReplacementsLevel1; private ConfigEntry<int> _maxReplacementsLevel2; private ConfigEntry<int> _maxReplacementsLevel3; private ConfigEntry<int> _maxReplacementsLevel4; private ConfigEntry<int> _maxDuplicatesLevel1; private ConfigEntry<int> _maxDuplicatesLevel2; private ConfigEntry<int> _maxDuplicatesLevel3; private ConfigEntry<int> _maxDuplicatesLevel4; private ConfigEntry<float> _maxReplacementFractionOfWave; private ConfigEntry<bool> _guaranteeSafeDuplicateAtLevel2; private ConfigEntry<bool> _allowNaturalSpawnBuffs; private ConfigEntry<float> _naturalEnrageChanceLevel2; private ConfigEntry<float> _naturalRadiantChanceLevel3; private ConfigEntry<bool> _allowHighMeterEnemyBuffs; private ConfigEntry<float> _enrageChanceAt300; private ConfigEntry<float> _enrageChanceAt1000; private ConfigEntry<float> _radiantChanceAt400; private ConfigEntry<float> _radiantChanceAt1000; private ConfigEntry<float> _radianceTier; private ConfigEntry<bool> _sanitizeInheritedIdolState; private ConfigEntry<float> _cerberusPairChance; private ConfigEntry<bool> _enableLargeEnemyPool; private ConfigEntry<float> _largeEnemyRarityMultiplier; private ConfigEntry<float> _largeEnemyGroundCheckDistance; private ConfigEntry<float> _largeEnemyActionPenalty; private ConfigEntry<int> _largeEnemyMinNonFilthWaveSize; private ConfigEntry<bool> _enableEnemyVarietyCooldown; private ConfigEntry<float> _enemyVarietyCooldownDecaySeconds; private ConfigEntry<float> _enemyVarietyCooldownStrength; private ConfigEntry<float> _largeEnemyCooldownBonus; private readonly HashSet<int> _processedEnemies = new HashSet<int>(); private readonly List<PendingWaveEnemy> _pendingWave = new List<PendingWaveEnemy>(); private readonly Dictionary<string, EnemyTemplatePool> _limitedEnemyPools = new Dictionary<string, EnemyTemplatePool>(); private readonly Dictionary<string, EnemyTemplatePool> _globalEnemyPools = new Dictionary<string, EnemyTemplatePool>(); private readonly HashSet<string> _loggedUnknownTypes = new HashSet<string>(); private readonly Dictionary<string, EnemyCooldownState> _enemyCooldowns = new Dictionary<string, EnemyCooldownState>(); private GUIStyle _hudStyle; private float _meter; private float _nextAllowedBonusSpawnTime; private float _sceneLoadedTime; private bool _globalPoolScannedThisScene; private string _currentSceneName = string.Empty; private bool _levelCompletionRewardGiven; private float _pendingWaveDeadline = -1f; private float _lastDamageTime = -999f; private float _lastDeathPenaltyTime = -999f; private int _consecutiveDamageHits; private bool _activeWaveLargeEnemySpawned; private int _activeWaveNonFilthEnemyCount; private void Awake() { //IL_0033: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfig(); _enemyIdentifierType = AccessTools.TypeByName("EnemyIdentifier"); _sceneLoadedTime = Time.time; Scene activeScene = SceneManager.GetActiveScene(); _currentSceneName = ((Scene)(ref activeScene)).name ?? string.Empty; ApplyStartingMeterFloor(); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("oai.ultrakill.leveldeath"); PatchSafely(); ((MonoBehaviour)this).StartCoroutine(PrimeGlobalEnemyPoolSoon()); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death loaded. Wave director enabled. Levels: Normal, Enraged, Radiant, Level Death."); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to unpatch Level Death cleanly: " + ex.Message)); } } private ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description) { return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description); } private void BindConfig() { _enabled = Bind("General", "Enabled", defaultValue: true, "Master switch for Level Death. Turning this off makes the mod inert and hides the HUD."); _enabled.SettingChanged += OnEnabledSettingChanged; _showHud = Bind("General", "ShowMeter", defaultValue: true, "Show the Level Death meter on screen."); _cyberGrindOnly = Bind("General", "CyberGrindOnly", defaultValue: false, "If true, Level Death only changes enemy spawns in Cyber Grind / Endless-style scenes."); _startingLevelMode = Bind("General", "StartingDifficulty", StartingLevelMode.Normal, "Starting meter floor for new scenes. Normal=0%, Enraged=100%, Radiant=200%, LevelDeath=300%, Custom=CustomStartingMeterPercent."); _startingMeterPercent = Bind("General", "CustomStartingMeterPercent", 0f, "Used only when StartingDifficulty is Custom. Clamped from 0% to 400%."); _enemyPoolMode = Bind("General", "EnemyPool", EnemyPoolMode.GlobalEnemies, "GlobalEnemies uses a curated global enemy pool. LimitedEnemyPool only clones enemies naturally present in the current scene/session."); _directorActionMode = Bind("General", "DirectorStyle", DirectorActionMode.MixedUpgradesAndReinforcements, "How Level Death spends pressure: AddReinforcementsOnly, MixedUpgradesAndReinforcements, or PreferUpgrades."); _debugLogging = Bind("General", "DebugLogging", defaultValue: false, "Verbose logs for wave decisions, replacement choices, damage penalties, and unknown enemy types."); _styleGainScale = Bind("Meter", "StyleGainScale", 0.02f, "Meter gained per style point before action bonuses are applied."); _softCapGainMultiplier = Bind("Meter", "GainMultiplierAbove100Percent", 0.82f, "Meter gain multiplier after Level 2 begins at 100%."); _levelCompletionReward = Bind("Meter", "LevelCompletionBonus", 25f, "Meter awarded once when the normal level-end rank screen appears."); _deathMeterRetainedMultiplier = Bind("Meter", "DeathMeterRetainedMultiplier", 0.5f, "Meter retained after death/respawn. Default 0.5 means dying halves the current Level Death percentage instead of resetting it to zero."); _damagePenaltyFlat = Bind("Meter", "BaseDamagePenalty", 6f, "Base meter removed when the player takes damage before isolated/consecutive hit scaling."); _damagePenaltyScale = Bind("Meter", "DamagePenaltyPerDamagePoint", 0.06f, "Extra meter removed per point of damage, when the damage value can be read."); _damagePenaltyMultiplier = Bind("Meter", "DamagePenaltyMultiplier", 1.3f, "Overall multiplier for damage meter loss. Default 1.3 makes damage remove about 30% more meter than previous versions."); _isolatedHitPenaltyMultiplier = Bind("Meter", "OccasionalHitPenaltyMultiplier", 0.25f, "Multiplier for isolated damage hits before streak scaling."); _consecutiveHitWindowSeconds = Bind("Meter", "ConsecutiveHitWindowSeconds", 4f, "Hits inside this window count as a damage streak and become increasingly punishing."); _consecutiveHitPenaltyStep = Bind("Meter", "ConsecutiveHitPenaltyStep", 0.35f, "Additional penalty multiplier per consecutive hit after the first."); _maxConsecutiveHitPenaltyMultiplier = Bind("Meter", "MaxConsecutiveHitPenaltyMultiplier", 1.8f, "Maximum multiplier for repeated hits in a short window."); _maxMeterCarriedBetweenScenes = Bind("Meter", "MaxMeterCarriedBetweenScenes", 400f, "Maximum meter preserved when loading a new scene. Level Death runs from 0% to 400%."); Bind("Meter", "OutOfCombatDecayPerSecond", 0f, "Deprecated/no-op. Time decay was removed so quiet levels do not punish the player."); Bind("Meter", "OutOfCombatDecayDelay", 0f, "Deprecated/no-op. Time decay was removed so quiet levels do not punish the player."); _minimumMeterForBonusSpawns = Bind("Director", "MinimumMeterForDirector", 18f, "Level Death will not add duplicate/replacement pressure below this meter value."); _toughPoolStartsAtMeter = Bind("Director", "ToughEnemiesOnlyAfterPercent", 9999f, "Deprecated/no-op. Low-tier enemies such as Filth, Strays, and Drones are no longer suppressed at high meter."); _globalEnemyMinimumMeter = Bind("Director", "GlobalEnemyAppearsAfterPercent", 70f, "Global-only enemy types cannot appear before this meter value, even when EnemyPool is GlobalEnemies."); _spawnChanceMultiplier = Bind("Director", "PressureChanceMultiplier", 1f, "Multiplies Level Death's duplicate/replacement chances."); _maxBonusBudget = Bind("Director", "BudgetAt100Percent", 12, "Base extra enemy-cost budget unlocked at 100% before the 100% budget jump."); _maxOvercapBonusBudget = Bind("Director", "BudgetAt400Percent", 38, "Maximum extra enemy-cost budget unlocked at 400%."); _deathMarkBudgetMultiplier = Bind("Director", "BudgetJumpAt100Percent", 2f, "When the meter reaches 100%, the director budget jumps by this multiplier before continuing toward the 400% cap."); _maxActiveBonusEnemies = Bind("Director", "MaxLivingBonusEnemies", 24, "Hard cap on living Level Death-created enemies."); _globalEnemyCostMultiplier = Bind("Director", "GlobalEnemyCostMultiplier", 1.75f, "Cost multiplier for enemies that are not already part of the current scene's limited pool."); _globalEnemyFlatSurcharge = Bind("Director", "GlobalEnemyCostBonus", 1, "Flat extra cost for global-only enemies. This keeps enemies like Ferryman expensive in tiny early rooms."); _minimumSecondsBetweenBonusSpawns = Bind("Director", "MinimumSecondsBetweenWaves", 0.45f, "Throttle after Level Death spawns duplicates/replacements."); _minimumSceneAgeForSpawns = Bind("Director", "SceneStartGraceSeconds", 3f, "Grace period after loading a scene before Level Death changes spawns. Natural enemies that appear during this grace period are now held and processed after the grace period instead of being discarded."); _waveBatchWindowSeconds = Bind("Director", "SpawnWaveReadSeconds", 1.45f, "How long Level Death keeps listening for drip-fed natural spawns before treating them as one wave. Higher values help rooms that drip-feed mixed enemies, but delay director actions slightly."); _lowTierWaveReadSeconds = Bind("Director", "LowTierSpawnWaveReadSeconds", 0.45f, "Shorter read window for waves made only of Filth/Strays/Drones. This prevents fast fodder from dying before Level Death can process it."); _processWaveImmediatelyAtSize = Bind("Director", "ProcessWaveImmediatelyAtSize", 4, "If a pending wave reaches this many enemies, process it almost immediately instead of waiting out the full read window."); Bind("Director", "WaveBatchWindowSeconds", 0.65f, "Deprecated/no-op. Use SpawnWaveReadSeconds instead."); _spawnRadiusMin = Bind("Director", "SpawnDistanceMin", 2.25f, "Minimum distance from the source spawn for bonus enemies."); _spawnRadiusMax = Bind("Director", "SpawnDistanceMax", 6.5f, "Maximum distance from the source spawn for bonus enemies."); _cerberusPairChance = Bind("Director", "CerberusPairChance", 0.55f, "When Level Death spawns a Cerberus, chance to spawn a second Cerberus nearby if safety caps allow it."); _enableEnemyVarietyCooldown = Bind("Director", "EnableHighCostEnemyCooldown", defaultValue: true, "Soft variety cooldown for enemies costing 7 or more. It never blocks a spawn, but heavily lowers odds after that enemy was recently selected."); _enemyVarietyCooldownDecaySeconds = Bind("Director", "HighCostEnemyCooldownDecaySeconds", 22f, "How quickly high-cost enemy cooldown pressure fades. Higher values keep rare enemies rare for longer."); _enemyVarietyCooldownStrength = Bind("Director", "HighCostEnemyCooldownStrength", 1f, "Overall strength of the high-cost enemy cooldown. 0 disables the effect even if enabled."); _largeEnemyCooldownBonus = Bind("Director", "LargeEnemyCooldownBonus", 3.5f, "Extra cooldown heat added to large enemies, making repeat large enemy picks substantially rarer."); _enableLargeEnemyPool = Bind("Large Enemies", "EnableLargeEnemyPool", defaultValue: true, "Allow very large enemies to appear in large-enough waves. They still require ground below and cannot replace flying enemies."); _largeEnemyRarityMultiplier = Bind("Large Enemies", "LargeEnemyRarityMultiplier", 0.5f, "Selection weight multiplier for large enemies. Default 0.5 makes them half as common as normal candidates."); _largeEnemyGroundCheckDistance = Bind("Large Enemies", "GroundCheckDistance", 10f, "Large enemies require ground below within this distance."); _largeEnemyActionPenalty = Bind("Large Enemies", "ActionPenaltyWhenLargeEnemySpawns", 1f, "When a large enemy spawns in a wave, reduce remaining duplicate/replacement caps by this amount."); _largeEnemyMinNonFilthWaveSize = Bind("Large Enemies", "MinimumNonFilthWaveSize", 5, "Large enemies may only appear in waves with this many non-Filth natural enemies. Filth are ignored because they are often spawned as swarms in small spaces."); _fullDirectorStartsAtWaveSize = Bind("Wave Rules", "FullDirectorStartsAtWaveSize", 4, "Waves this large use the normal per-level duplicate/replacement caps. Smaller waves are protected from obvious cloning/randomizer behavior."); _mediumWaveMaxDuplicates = Bind("Wave Rules", "MediumWaveMaxDuplicates", 1, "Maximum duplicates for small waves below FullDirectorStartsAtWaveSize but larger than one enemy. Default means 2-3 enemy waves can only get one duplicate."); _mediumWaveMaxReplacements = Bind("Wave Rules", "MediumWaveMaxReplacements", 1, "Maximum type replacements for small waves below FullDirectorStartsAtWaveSize but larger than one enemy. Default means 2-3 enemy waves can only get one replacement."); _guaranteeSafeDuplicateAtLevel2 = Bind("Wave Rules", "GuaranteeDuplicateAt100Percent", defaultValue: true, "At 100% and above, try to add one safe local duplicate, but only when the natural wave has at least two enemies."); _allowSingleFodderReplacement = Bind("Wave Rules", "AllowSingleFodderReplacement", defaultValue: true, "Allow lone Filth/Stray/Drone spawns to be type-replaced at 100%+. They still cannot be duplicated. This keeps drip-fed fodder waves active without cloning single scripted enemies."); _maxReplacementFractionOfWave = Bind("Wave Rules", "MaxReplacementFractionOfWave", 0.35f, "Personality-preservation cap. By default, no more than about 35% of a natural wave is type-replaced."); _maxDuplicatesLevel1 = Bind("Duplicate Caps", "Level1Normal", 1, "Maximum safe local duplicates per eligible spawn wave in Level 1: Normal. Single-enemy waves still receive zero duplicates."); _maxDuplicatesLevel2 = Bind("Duplicate Caps", "Level2Enraged", 2, "Maximum safe local duplicates per eligible spawn wave in Level 2: Enraged."); _maxDuplicatesLevel3 = Bind("Duplicate Caps", "Level3Radiant", 3, "Maximum safe local duplicates per eligible spawn wave in Level 3: Radiant."); _maxDuplicatesLevel4 = Bind("Duplicate Caps", "LevelDeath", 3, "Maximum safe local duplicates per eligible spawn wave in Level Death. Kept at 3 by default so Level Death favors quality over spam."); _allowTypeReplacements = Bind("Replacements", "AllowTypeReplacements", defaultValue: true, "Allow Level Death to replace some natural enemy spawns with more expensive global-pool enemies. Turn this off if a level behaves strangely."); _replacementChanceLevel2 = Bind("Replacements", "Level2EnragedChance", 0.45f, "Per-wave chance for each eligible natural spawn to be replaced by a tougher enemy in Level 2: Enraged, up to the cap."); _replacementChanceLevel3 = Bind("Replacements", "Level3RadiantChance", 0.62f, "Per-wave chance for each eligible natural spawn to be replaced by a tougher enemy in Level 3: Radiant, up to the cap."); _replacementChanceLevel4 = Bind("Replacements", "LevelDeathChance", 0.74f, "Per-wave chance for each eligible natural spawn to be replaced by a tougher enemy in Level Death, up to the cap."); _maxReplacementsLevel1 = Bind("Replacement Caps", "Level1Normal", 0, "Maximum type replacements per eligible spawn wave in Level 1: Normal."); _maxReplacementsLevel2 = Bind("Replacement Caps", "Level2Enraged", 1, "Maximum type replacements per eligible spawn wave in Level 2: Enraged."); _maxReplacementsLevel3 = Bind("Replacement Caps", "Level3Radiant", 3, "Maximum type replacements per eligible spawn wave in Level 3: Radiant."); _maxReplacementsLevel4 = Bind("Replacement Caps", "LevelDeath", 4, "Maximum type replacements per eligible spawn wave in Level Death."); _allowNaturalSpawnBuffs = Bind("Enemy Upgrades", "AllowNaturalEnemyUpgrades", defaultValue: true, "Allow natural, non-Level-Death spawns to be upgraded in place. This reduces enemy spam by making existing enemies more dangerous."); _naturalEnrageChanceLevel2 = Bind("Enemy Upgrades", "NaturalEnrageChance", 0.16f, "Chance for a natural spawn to become enraged in Level 2: Enraged and above."); _naturalRadiantChanceLevel3 = Bind("Enemy Upgrades", "NaturalRadiantChance", 0.14f, "Chance for a natural spawn to receive tier-1 radiant buffs in Level 3: Radiant and above."); _allowHighMeterEnemyBuffs = Bind("Enemy Upgrades", "AllowBonusEnemyUpgrades", defaultValue: true, "Allow Level Death-created enemies to occasionally become enraged or radiant."); _enrageChanceAt300 = Bind("Enemy Upgrades", "BonusEnemyEnrageChanceAt100Percent", 0.1f, "Chance for a Level Death-created reinforcement to be enraged at 100%, the start of Level 2."); _enrageChanceAt1000 = Bind("Enemy Upgrades", "BonusEnemyEnrageChanceAt400Percent", 0.3f, "Chance for a Level Death-created reinforcement to be enraged at 400%, the Level Death cap."); _radiantChanceAt400 = Bind("Enemy Upgrades", "BonusEnemyRadiantChanceAt200Percent", 0.08f, "Chance for a Level Death-created reinforcement to receive tier-1 radiant buffs at 200%, the start of Level 3."); _radiantChanceAt1000 = Bind("Enemy Upgrades", "BonusEnemyRadiantChanceAt400Percent", 0.22f, "Chance for a Level Death-created reinforcement to receive tier-1 radiant buffs at 400%."); _radianceTier = Bind("Enemy Upgrades", "RadianceTier", 1f, "Radiance tier assigned to radiant Level Death enemies. Default is true tier 1 radiance."); _allowUnknownEnemyTypes = Bind("Advanced Safety", "AllowUnknownEnemyTypes", defaultValue: false, "Unknown enemy types are ignored by default because custom/scripted enemies are more likely to break when cloned."); _sanitizeInheritedIdolState = Bind("Advanced Safety", "RemoveInheritedIdolBlessingFromClones", defaultValue: true, "When Level Death clones an enemy, clear inherited Idol/blessed runtime state so duplicates do not become invulnerable just because the source was protected by an Idol."); } private void OnEnabledSettingChanged(object sender, EventArgs e) { if (_enabled != null && !_enabled.Value) { _meter = 0f; _pendingWave.Clear(); _pendingWaveDeadline = -1f; _consecutiveDamageHits = 0; _lastDamageTime = -999f; _nextAllowedBonusSpawnTime = Time.time + Mathf.Max(0.5f, (_minimumSecondsBetweenBonusSpawns != null) ? _minimumSecondsBetweenBonusSpawns.Value : 1f); if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death disabled from config; meter reset and future spawns halted."); } } else if (_enabled != null && _enabled.Value) { _sceneLoadedTime = Time.time; _nextAllowedBonusSpawnTime = Time.time + ((_minimumSceneAgeForSpawns != null) ? Mathf.Max(0f, _minimumSceneAgeForSpawns.Value) : 3f); if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death enabled from config; spawns will resume after the scene grace period."); } } } private void PatchSafely() { PatchFirstMethod(GetEnemyIdentifierType(), "Start", "EnemyStartPostfix", required: true); PatchFirstMethod(AccessTools.TypeByName("StyleHUD"), "AddPoints", "StyleAddPointsPostfix", required: false); PatchAllNamedMethods(AccessTools.TypeByName("NewMovement"), "GetHurt", "PlayerHurtPostfix", required: false); PatchAllNamedMethods(AccessTools.TypeByName("NewMovement"), "Respawn", "PlayerDeathOrRespawnPostfix", required: false); PatchAllNamedMethods(AccessTools.TypeByName("NewMovement"), "Die", "PlayerDeathOrRespawnPostfix", required: false); PatchAllNamedMethods(AccessTools.TypeByName("FinalRank"), "SetInfo", "LevelCompletePostfix", required: false); } 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(LevelDeathPlugin).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 void PatchAllNamedMethods(Type type, string methodName, string patchMethodName, bool required) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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; } HarmonyMethod val = new HarmonyMethod(typeof(LevelDeathPlugin).GetMethod(patchMethodName, BindingFlags.Static | BindingFlags.NonPublic)); int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (!(declaredMethod == null) && !(declaredMethod.Name != methodName)) { try { _harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Skipped patch " + type.Name + "." + methodName + ": " + ex.Message)); } } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + num + " overload(s) of " + type.Name + "." + methodName + ".")); } else if (required) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find required method " + type.Name + "." + methodName + ".")); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Optional patch not found: " + type.Name + "." + methodName + ".")); } } private void Update() { ProcessPendingWaveIfReady(); } private void OnGUI() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_005f: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) if (_showHud.Value && _enabled.Value) { if (_hudStyle == null) { _hudStyle = new GUIStyle(GUI.skin.box) { fontSize = 18, alignment = (TextAnchor)3, richText = true, padding = new RectOffset(10, 10, 8, 8) }; } string rankName = GetRankName(); string text = (CanSpawnInCurrentScene() ? string.Empty : "\n<size=12>spawns gated</size>"); string text2 = ((_enemyPoolMode.Value == EnemyPoolMode.GlobalEnemies) ? "Global" : "Limited"); GUI.Box(new Rect(20f, 20f, 340f, 70f), "<b>LEVEL DEATH</b> " + rankName + " <size=12>" + text2 + "</size>\n" + Mathf.RoundToInt(_meter) + "% " + MakeMeterBar(_meter) + text, _hudStyle); } } private static string MakeMeterBar(float value) { int num = Mathf.Clamp(Mathf.RoundToInt(((value >= 400f) ? 100f : Mathf.Repeat(Mathf.Max(0f, value), 100f)) / 10f), 0, 10); return new string('|', num) + new string('.', 10 - num); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { _processedEnemies.Clear(); _pendingWave.Clear(); _pendingWaveDeadline = -1f; _enemyCooldowns.Clear(); _limitedEnemyPools.Clear(); _loggedUnknownTypes.Clear(); LevelDeathSpawnedMarker.ActiveCount = 0; _meter = Mathf.Min(_meter, Mathf.Clamp((_maxMeterCarriedBetweenScenes != null) ? _maxMeterCarriedBetweenScenes.Value : 400f, 0f, 400f)); ApplyStartingMeterFloor(); _sceneLoadedTime = Time.time; _globalPoolScannedThisScene = false; _currentSceneName = ((Scene)(ref scene)).name ?? string.Empty; _levelCompletionRewardGiven = false; _consecutiveDamageHits = 0; _lastDamageTime = -999f; _nextAllowedBonusSpawnTime = Time.time + _minimumSceneAgeForSpawns.Value; ((MonoBehaviour)this).StartCoroutine(PrimeGlobalEnemyPoolSoon()); } private static void EnemyStartPostfix(object __instance) { try { Instance?.OnEnemyStarted(__instance); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Level Death EnemyStartPostfix failed: " + ex)); } } } private static void StyleAddPointsPostfix(object[] __args) { try { if (!((Object)(object)Instance == (Object)null) && __args != null && __args.Length != 0) { int points = ReadIntLike(__args[0]); string text = ((__args.Length > 1) ? Convert.ToString(__args[1]) : string.Empty); Instance.OnStylePoints(points, text ?? string.Empty); } } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Level Death StyleAddPointsPostfix failed: " + ex)); } } } private static void PlayerHurtPostfix(object[] __args) { try { if ((Object)(object)Instance == (Object)null) { return; } float num = 0f; if (__args != null) { foreach (object obj in __args) { if (obj is int num2) { num = Mathf.Max(num, (float)num2); break; } if (obj is float num3) { num = Mathf.Max(num, num3); break; } } } Instance.OnPlayerHurt(num); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Level Death PlayerHurtPostfix failed: " + ex)); } } } private static void PlayerDeathOrRespawnPostfix() { try { Instance?.OnPlayerDeathOrRespawn(); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Level Death PlayerDeathOrRespawnPostfix failed: " + ex)); } } } private static void LevelCompletePostfix() { try { Instance?.OnLevelComplete(); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Level Death LevelCompletePostfix failed: " + ex)); } } } private static int ReadIntLike(object value) { if (value is int) { return (int)value; } if (value is float num) { return Mathf.RoundToInt(num); } if (value is double a) { return (int)Math.Round(a); } return 0; } private void OnStylePoints(int points, string styleKey) { if (_enabled.Value && points > 0) { string text = Normalize(styleKey); float num = Mathf.Clamp((float)points * _styleGainScale.Value, 0.05f, 3.5f); if (text.Contains("parry")) { num += 2.25f; } if (text.Contains("projectileboost")) { num += 0.75f; } if (text.Contains("kill")) { num += 0.8f; } if (text.Contains("doublekill") || text.Contains("triplekill") || text.Contains("multikill")) { num += 1.2f; } if (text.Contains("air") || text.Contains("arsenal") || text.Contains("ultra")) { num += 0.35f; } AddMeter(num); } } private void OnPlayerHurt(float damage) { if (_enabled.Value) { float time = Time.time; float num = Mathf.Max(0.25f, _consecutiveHitWindowSeconds.Value); if (time - _lastDamageTime > num) { _consecutiveDamageHits = 0; } _consecutiveDamageHits++; _lastDamageTime = time; float num2 = _damagePenaltyFlat.Value + Mathf.Max(0f, damage) * _damagePenaltyScale.Value; float num3 = Mathf.Clamp(_isolatedHitPenaltyMultiplier.Value, 0f, 1f); float num4 = Mathf.Max(0f, _consecutiveHitPenaltyStep.Value); float num5 = Mathf.Max(num3, _maxConsecutiveHitPenaltyMultiplier.Value); float num6 = Mathf.Clamp(num3 + (float)(_consecutiveDamageHits - 1) * num4, num3, num5); float num7 = num2 * num6 * Mathf.Max(0f, (_damagePenaltyMultiplier != null) ? _damagePenaltyMultiplier.Value : 1.3f); AddMeter(0f - num7); if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death damage penalty: damage=" + damage + ", streak=" + _consecutiveDamageHits + ", multiplier=" + num6.ToString("0.00") + ", penalty=" + num7.ToString("0.0") + ", meter=" + Mathf.RoundToInt(_meter) + "%.")); } } } private void OnPlayerDeathOrRespawn() { if (!_enabled.Value) { return; } float time = Time.time; if (!(time - _lastDeathPenaltyTime < 1.25f)) { _lastDeathPenaltyTime = time; float num = Mathf.Clamp01((_deathMeterRetainedMultiplier != null) ? _deathMeterRetainedMultiplier.Value : 0.5f); _meter = Mathf.Clamp(_meter * num, 0f, 400f); _pendingWave.Clear(); _pendingWaveDeadline = -1f; _consecutiveDamageHits = 0; _lastDamageTime = -999f; _nextAllowedBonusSpawnTime = Time.time + 3f; if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death death/respawn penalty applied. Retained " + Mathf.RoundToInt(num * 100f) + "%; meter=" + Mathf.RoundToInt(_meter) + "%.")); } } } private void OnLevelComplete() { if (_enabled.Value && !_levelCompletionRewardGiven) { _levelCompletionRewardGiven = true; AddMeter(Mathf.Max(0f, _levelCompletionReward.Value), applySoftCap: false); if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death awarded level completion reward. Meter=" + Mathf.RoundToInt(_meter) + "%.")); } } } private void AddMeter(float delta, bool applySoftCap = true) { if (applySoftCap && delta > 0f && _meter >= 100f) { delta *= Mathf.Clamp(_softCapGainMultiplier.Value, 0.01f, 1f); } else if (applySoftCap && delta > 0f && _meter + delta > 100f) { float num = 100f - _meter; float num2 = (_meter + delta - 100f) * Mathf.Clamp(_softCapGainMultiplier.Value, 0.01f, 1f); delta = Mathf.Max(0f, num) + num2; } _meter = Mathf.Clamp(_meter + delta, 0f, 400f); } 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 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 bool IsTemplateSafeToSpawn(GameObject template, string key) { if ((Object)(object)template == (Object)null) { return false; } if ((Object)(object)template.GetComponent<LevelDeathSpawnedMarker>() != (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) { return false; } if (!clone.activeInHierarchy) { return false; } if (IsHardBlockedTemplateName((key ?? string.Empty) + " " + ((Object)clone).name)) { return false; } if (!HasEnemyIdentifierComponent(clone)) { return false; } return true; } private void DestroyCloneIfPresent(GameObject clone) { if ((Object)(object)clone != (Object)null) { Object.Destroy((Object)(object)clone); } } [IteratorStateMachine(typeof(<DestroyOriginalAfterReplacementValid>d__131))] private IEnumerator DestroyOriginalAfterReplacementValid(GameObject source, GameObject clone, string key) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DestroyOriginalAfterReplacementValid>d__131(0) { <>4__this = this, source = source, clone = clone, key = key }; } private void OnEnemyStarted(object enemyObject) { if (_enabled == null || !_enabled.Value || 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<LevelDeathSpawnedMarker>() != (Object)null || ((Object)gameObject).name.IndexOf(" [Level Death]", StringComparison.OrdinalIgnoreCase) >= 0) { return; } if (IsHardBlockedTemplateName(((Object)gameObject).name)) { LogUnknownOrBlocked(string.Empty, ((Object)gameObject).name, "hard-blocked name"); return; } string text = ReadEnemyTypeName(enemyObject, gameObject); EnemyProfile profile = EnemyCatalog.GetProfile(text, ((Object)gameObject).name, _allowUnknownEnemyTypes.Value); if (!profile.Allowed) { LogUnknownOrBlocked(text, ((Object)gameObject).name, profile.BlockReason); return; } RegisterLimitedTemplate(profile.Key, gameObject, profile.Cost); RegisterGlobalTemplate(profile.Key, gameObject, profile.Cost, allowInactiveTemplate: false); if (CanSpawnInCurrentScene() && _enemyPoolMode.Value == EnemyPoolMode.GlobalEnemies) { ScanGlobalEnemyPoolOncePerScene(); } QueueNaturalEnemyForWave(gameObject, profile); } private void QueueNaturalEnemyForWave(GameObject source, EnemyProfile profile) { if (!((Object)(object)source == (Object)null) && source.activeInHierarchy && 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 || !IsFastFodderKey(pendingWaveEnemy.Profile.Key)) { flag = false; break; } } float num = Mathf.Clamp((_waveBatchWindowSeconds != null) ? _waveBatchWindowSeconds.Value : 1.45f, 0.1f, 4f); float num2 = Mathf.Clamp((_lowTierWaveReadSeconds != null) ? _lowTierWaveReadSeconds.Value : 0.45f, 0.05f, num); float num3 = (flag ? num2 : num); int num4 = Mathf.Max(2, (_processWaveImmediatelyAtSize != null) ? _processWaveImmediatelyAtSize.Value : 4); if (_pendingWave.Count >= num4) { num3 = Mathf.Min(num3, 0.12f); } _pendingWaveDeadline = Time.time + num3; } private bool ShouldFlushPendingWaveEarly() { if (_pendingWave.Count == 0) { return false; } float num = Time.time; bool flag = true; bool flag2 = false; for (int i = 0; i < _pendingWave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = _pendingWave[i]; if (pendingWaveEnemy != null) { num = Mathf.Min(num, pendingWaveEnemy.TimeStarted); if (!IsFastFodderKey(pendingWaveEnemy.Profile.Key)) { flag = false; } if ((Object)(object)pendingWaveEnemy.Source == (Object)null || !pendingWaveEnemy.Source.activeInHierarchy) { flag2 = true; } } } float num2 = Time.time - num; int num3 = Mathf.Max(2, (_processWaveImmediatelyAtSize != null) ? _processWaveImmediatelyAtSize.Value : 4); if (_pendingWave.Count >= num3 && num2 >= 0.08f) { return true; } if (flag && flag2 && num2 >= 0.08f) { return true; } return false; } private bool IsFastFodderKey(string key) { key = Normalize(key); if (!(key == "filth") && !(key == "stray")) { return key == "drone"; } return true; } private bool IsEntrySourceAlive(PendingWaveEnemy entry) { if (entry != null && (Object)(object)entry.Source != (Object)null) { return entry.Source.activeInHierarchy; } return false; } private void ProcessPendingWaveIfReady() { if (_pendingWave.Count == 0) { return; } if (!_enabled.Value) { _pendingWave.Clear(); _pendingWaveDeadline = -1f; } else { if (Time.time < _pendingWaveDeadline && !ShouldFlushPendingWaveEarly()) { return; } if (!CanSpawnInCurrentScene()) { _pendingWaveDeadline = Time.time + 0.25f; return; } List<PendingWaveEnemy> list = new List<PendingWaveEnemy>(); 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<LevelDeathSpawnedMarker>() != (Object)null) && (((Object)pendingWaveEnemy.Source).name ?? string.Empty).IndexOf(" [Level Death]", StringComparison.OrdinalIgnoreCase) < 0))) { list.Add(pendingWaveEnemy); } } _pendingWave.Clear(); _pendingWaveDeadline = -1f; if (list.Count != 0) { if (_enemyPoolMode.Value == EnemyPoolMode.GlobalEnemies) { ScanGlobalEnemyPoolOncePerScene(); } int levelIndex = GetLevelIndex(); HashSet<int> replacedIds = new HashSet<int>(); _activeWaveNonFilthEnemyCount = CountNonFilthEnemies(list); _activeWaveLargeEnemySpawned = false; int num = TryApplyWaveReplacements(list, replacedIds, levelIndex); int num2 = TryApplyWaveNaturalBuffs(list, replacedIds, levelIndex); int num3 = TryApplyWaveDuplicates(list, replacedIds, levelIndex); if (_debugLogging.Value && (num > 0 || num2 > 0 || num3 > 0)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death processed wave: natural=" + list.Count + ", level=" + GetRankName() + ", replacements=" + num + ", naturalBuffs=" + num2 + ", duplicates=" + num3 + ", meter=" + Mathf.RoundToInt(_meter) + "%.")); } _activeWaveLargeEnemySpawned = false; _activeWaveNonFilthEnemyCount = 0; } } } private List<PendingWaveEnemy> GetWaveOrderedByCost(List<PendingWaveEnemy> wave, bool ascending) { List<PendingWaveEnemy> list = new List<PendingWaveEnemy>(); if (wave == null) { return list; } for (int i = 0; i < wave.Count; i++) { if (wave[i] != null) { list.Add(wave[i]); } } list.Sort((PendingWaveEnemy a, PendingWaveEnemy b) => (!ascending) ? b.Profile.Cost.CompareTo(a.Profile.Cost) : a.Profile.Cost.CompareTo(b.Profile.Cost)); return list; } private bool IsMauriceProfile(EnemyProfile profile) { if (!(profile.Key == "maurice")) { return profile.Key == "maliciousface"; } return true; } private int CountMaurices(List<PendingWaveEnemy> wave) { if (wave == null) { return 0; } int num = 0; for (int i = 0; i < wave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = wave[i]; if (pendingWaveEnemy != null && IsMauriceProfile(pendingWaveEnemy.Profile)) { num++; } } return num; } private int CountNonFilthEnemies(List<PendingWaveEnemy> wave) { if (wave == null) { return 0; } 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 int TryApplyWaveReplacements(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int level) { if (!_allowTypeReplacements.Value) { return 0; } if (_enemyPoolMode.Value != EnemyPoolMode.GlobalEnemies) { return 0; } if (_directorActionMode.Value == DirectorActionMode.AddReinforcementsOnly) { return 0; } if (_meter < 100f) { return 0; } if (LevelDeathSpawnedMarker.ActiveCount >= _maxActiveBonusEnemies.Value) { return 0; } int num = ApplyLargeEnemyActionPenalty(GetReplacementCapForLevel(level, wave.Count)); if (num <= 0 && IsEligibleSingleFodderReplacementWave(wave, level)) { num = 1; } if (num <= 0) { return 0; } float typeReplacementChance = GetTypeReplacementChance(); bool flag = CountMaurices(wave) >= 2; int num2 = 0; List<PendingWaveEnemy> waveOrderedByCost = GetWaveOrderedByCost(wave, ascending: true); for (int i = 0; i < waveOrderedByCost.Count; i++) { PendingWaveEnemy pendingWaveEnemy = waveOrderedByCost[i]; if (num2 >= num) { break; } bool flag2 = IsEntrySourceAlive(pendingWaveEnemy); if ((flag && IsMauriceProfile(pendingWaveEnemy.Profile)) || (flag2 && ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source)) || (pendingWaveEnemy.Profile.Cost >= 10 && _meter < 300f) || Random.value > typeReplacementChance) { continue; } EnemySpawnCandidate enemySpawnCandidate = ChooseReplacementCandidate(pendingWaveEnemy.Profile, flag2 ? pendingWaveEnemy.Source : null); if (enemySpawnCandidate == null || enemySpawnCandidate.BaseCost <= pendingWaveEnemy.Profile.Cost || !CanCandidateReplaceSource(enemySpawnCandidate, pendingWaveEnemy.Profile, flag2 ? pendingWaveEnemy.Source : null)) { continue; } int sourceId = pendingWaveEnemy.SourceId; if (SpawnReplacement(pendingWaveEnemy, enemySpawnCandidate.Pool, pendingWaveEnemy.Profile, enemySpawnCandidate)) { replacedIds.Add(sourceId); num2++; if (_activeWaveLargeEnemySpawned) { num = ApplyLargeEnemyActionPenalty(num); } } } return num2; } private bool IsEligibleSingleFodderReplacementWave(List<PendingWaveEnemy> wave, int level) { if (_allowSingleFodderReplacement == null || !_allowSingleFodderReplacement.Value) { return false; } if (level < 2 || _meter < 100f) { return false; } if (wave == null || wave.Count != 1 || wave[0] == null) { return false; } return IsFastFodderKey(wave[0].Profile.Key); } private int ApplyLargeEnemyActionPenalty(int cap) { if (!_activeWaveLargeEnemySpawned) { return cap; } int num = Mathf.Max(0, Mathf.RoundToInt((_largeEnemyActionPenalty != null) ? _largeEnemyActionPenalty.Value : 1f)); return Mathf.Max(0, cap - num); } private int TryApplyWaveNaturalBuffs(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int level) { if (!_allowNaturalSpawnBuffs.Value) { return 0; } if (_directorActionMode.Value == DirectorActionMode.AddReinforcementsOnly) { return 0; } if (level < 2) { return 0; } int num = 0; int num2 = Mathf.Clamp(level, 1, 4); List<PendingWaveEnemy> waveOrderedByCost = GetWaveOrderedByCost(wave, ascending: false); for (int i = 0; i < waveOrderedByCost.Count; i++) { PendingWaveEnemy pendingWaveEnemy = waveOrderedByCost[i]; if (num >= num2) { break; } if (!replacedIds.Contains(pendingWaveEnemy.SourceId) && !((Object)(object)pendingWaveEnemy.Source == (Object)null) && pendingWaveEnemy.Source.activeInHierarchy) { bool flag = false; if (level >= 3 && Random.value < GetNaturalRadiantChance()) { ApplyRadiantLikeBuff(pendingWaveEnemy.Source); flag = true; } else if (Random.value < GetNaturalEnrageChance()) { ApplyEnrage(pendingWaveEnemy.Source); flag = true; } if (flag) { num++; } } } return num; } private int TryApplyWaveDuplicates(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int level) { if (_meter < _minimumMeterForBonusSpawns.Value) { return 0; } if (Time.time < _nextAllowedBonusSpawnTime && (level < 2 || !_guaranteeSafeDuplicateAtLevel2.Value)) { return 0; } if (LevelDeathSpawnedMarker.ActiveCount >= _maxActiveBonusEnemies.Value) { return 0; } int num = ApplyLargeEnemyActionPenalty(GetDuplicateCapForLevel(level, wave.Count)); if (num <= 0) { return 0; } List<PendingWaveEnemy> list = new List<PendingWaveEnemy>(); for (int i = 0; i < wave.Count; i++) { PendingWaveEnemy pendingWaveEnemy = wave[i]; if (pendingWaveEnemy != null && (Object)(object)pendingWaveEnemy.Source != (Object)null && pendingWaveEnemy.Source.activeInHierarchy && !replacedIds.Contains(pendingWaveEnemy.SourceId) && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source)) { list.Add(pendingWaveEnemy); } } if (list.Count == 0) { return 0; } int j = 0; bool flag = level >= 2 && _guaranteeSafeDuplicateAtLevel2.Value && wave.Count >= 2; if (flag) { j = 1; } else if (Random.value < GetWaveDuplicateChance(level)) { j = 1; } for (; j < num && Random.value < GetAdditionalDuplicateChance(level, j); j++) { } int num2 = 0; int num3 = Mathf.Max(1, GetBonusBudgetForMeter()); int num4 = 0; while (num2 < j && num4 < j * 4 + 4 && LevelDeathSpawnedMarker.ActiveCount < _maxActiveBonusEnemies.Value) { num4++; PendingWaveEnemy pendingWaveEnemy2 = ChooseLocalDuplicateSource(list, num3, num2 == 0 && flag); if (pendingWaveEnemy2 == null) { break; } if (SpawnDuplicateOfNatural(pendingWaveEnemy2.Source, num2)) { num2++; RecordEnemySelectionCooldown(pendingWaveEnemy2.Profile.Key, pendingWaveEnemy2.Profile.Cost, pendingWaveEnemy2.Profile.IsLarge); num3 -= Mathf.Max(1, pendingWaveEnemy2.Profile.Cost); if (_activeWaveLargeEnemySpawned) { j = Mathf.Min(j, ApplyLargeEnemyActionPenalty(num)); } } } if (num2 > 0) { _nextAllowedBonusSpawnTime = Time.time + Mathf.Max(0.05f, _minimumSecondsBetweenBonusSpawns.Value); } return num2; } private PendingWaveEnemy ChooseLocalDuplicateSource(List<PendingWaveEnemy> alive, int remainingBudget, bool safetyDuplicate) { if (alive == null || alive.Count == 0) { return null; } List<WeightedWaveEnemy> list = new List<WeightedWaveEnemy>(); bool flag = !safetyDuplicate && _meter >= _toughPoolStartsAtMeter.Value; bool flag2 = false; if (flag) { for (int i = 0; i < alive.Count; i++) { PendingWaveEnemy pendingWaveEnemy = alive[i]; if (pendingWaveEnemy != null && !ShouldSuppressLowTier(pendingWaveEnemy.Profile.Key)) { flag2 = true; break; } } } for (int j = 0; j < alive.Count; j++) { PendingWaveEnemy pendingWaveEnemy2 = alive[j]; if (pendingWaveEnemy2 != null && !((Object)(object)pendingWaveEnemy2.Source == (Object)null) && pendingWaveEnemy2.Source.activeInHierarchy && (safetyDuplicate || pendingWaveEnemy2.Profile.Cost <= remainingBudget) && (!(flag && flag2) || !ShouldSuppressLowTier(pendingWaveEnemy2.Profile.Key))) { float num = 1f; if (pendingWaveEnemy2.Profile.Cost >= 5) { num *= 1.25f; } if (pendingWaveEnemy2.Profile.Key == "cerberus") { num *= 1.35f; } num *= GetEnemyVarietyCooldownWeight(pendingWaveEnemy2.Profile.Key, pendingWaveEnemy2.Profile.Cost, pendingWaveEnemy2.Profile.IsLarge); list.Add(new WeightedWaveEnemy(pendingWaveEnemy2, num)); } } if (list.Count == 0 && safetyDuplicate) { for (int k = 0; k < alive.Count; k++) { PendingWaveEnemy pendingWaveEnemy3 = alive[k]; if (pendingWaveEnemy3 != null && (Object)(object)pendingWaveEnemy3.Source != (Object)null && pendingWaveEnemy3.Source.activeInHierarchy && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy3.Source)) { list.Add(new WeightedWaveEnemy(pendingWaveEnemy3, 1f)); } } } if (list.Count == 0) { return null; } float num2 = 0f; for (int l = 0; l < list.Count; l++) { num2 += list[l].Weight; } float num3 = Random.value * num2; for (int m = 0; m < list.Count; m++) { num3 -= list[m].Weight; if (num3 <= 0f) { return list[m].Enemy; } } return list[list.Count - 1].Enemy; } private bool SpawnDuplicateOfNatural(GameObject source, int index) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return false; } if (IsHardBlockedTemplateName(((Object)source).name) || !IsTemplateSafeToSpawn(source, string.Empty)) { return false; } try { Vector3 val = PickSpawnPosition(source.transform.position, index); Quaternion rotation = source.transform.rotation; Transform parent = source.transform.parent; GameObject val2 = Object.Instantiate<GameObject>(source, val, rotation, parent); ((Object)val2).name = ((Object)source).name + " [Level Death]"; val2.AddComponent<LevelDeathSpawnedMarker>(); SanitizeClonedEnemyRuntimeState(val2); if (!val2.activeSelf) { val2.SetActive(true); } if (!IsSpawnedCloneValid(val2, string.Empty)) { DestroyCloneIfPresent(val2); if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death rejected a natural duplicate clone that did not validate: " + ((Object)source).name)); } return false; } TryApplyHighMeterSpawnBuffs(val2); if (EnemyCatalog.IsLargeKey(EnemyCatalog.GetProfile(string.Empty, ((Object)val2).name, allowUnknown: false).Key)) { _activeWaveLargeEnemySpawned = true; } TrySpawnCerberusPairBonus(val2, source.transform.position, parent, null); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death refused a local duplicate because Instantiate failed: " + ex.Message)); return false; } } private void TrySpawnCerberusPairBonus(GameObject spawned, Vector3 sourcePosition, Transform parent, EnemyTemplatePool preferredPool) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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) if ((Object)(object)spawned == (Object)null || LevelDeathSpawnedMarker.ActiveCount >= _maxActiveBonusEnemies.Value || Normalize(((Object)spawned).name).IndexOf("cerberus", StringComparison.OrdinalIgnoreCase) < 0 || Random.value > Mathf.Clamp01((_cerberusPairChance != null) ? _cerberusPairChance.Value : 0.55f)) { return; } try { GameObject val = spawned; if (preferredPool != null && preferredPool.HasUsableTemplate) { GameObject randomLiveTemplate = preferredPool.GetRandomLiveTemplate(); if ((Object)(object)randomLiveTemplate != (Object)null) { val = randomLiveTemplate; } } Vector3 val2 = PickSpawnPosition(sourcePosition, 2 + Random.Range(0, 3)); GameObject val3 = Object.Instantiate<GameObject>(val, val2, spawned.transform.rotation, parent); ((Object)val3).name = ((Object)val).name + " [Level Death Cerberus Pair]"; val3.AddComponent<LevelDeathSpawnedMarker>(); SanitizeClonedEnemyRuntimeState(val3); if (!val3.activeSelf) { val3.SetActive(true); } if (!IsSpawnedCloneValid(val3, "cerberus")) { DestroyCloneIfPresent(val3); if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death rejected a paired Cerberus clone that did not validate."); } } else { TryApplyHighMeterSpawnBuffs(val3); if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death spawned a paired Cerberus."); } } } catch (Exception ex) { if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death skipped Cerberus pair spawn safely: " + ex.Message)); } } } private int GetReplacementCapForLevel(int level, int waveCount) { if (waveCount <= 1) { return 0; } int num = ((level <= 1) ? _maxReplacementsLevel1.Value : (level switch { 2 => _maxReplacementsLevel2.Value, 3 => _maxReplacementsLevel3.Value, _ => _maxReplacementsLevel4.Value, })); if (num <= 0) { return 0; } int num2 = Mathf.Max(2, (_fullDirectorStartsAtWaveSize != null) ? _fullDirectorStartsAtWaveSize.Value : 4); if (waveCount < num2) { num = Mathf.Min(num, Mathf.Max(0, (_mediumWaveMaxReplacements == null) ? 1 : _mediumWaveMaxReplacements.Value)); } float num3 = Mathf.Clamp(_maxReplacementFractionOfWave.Value, 0.05f, 1f); int num4 = Mathf.Max(1, Mathf.FloorToInt((float)waveCount * num3)); if (level >= 4 && waveCount >= num2) { num4 = Mathf.Max(num4, Mathf.Min(num, Mathf.CeilToInt((float)waveCount * 0.45f))); } return Mathf.Clamp(num, 0, num4); } private int GetDuplicateCapForLevel(int level, int waveCount) { if (waveCount <= 1) { return 0; } int num = ((level <= 1) ? Mathf.Max(0, _maxDuplicatesLevel1.Value) : (level switch { 2 => Mathf.Max(0, _maxDuplicatesLevel2.Value), 3 => Mathf.Max(0, _maxDuplicatesLevel3.Value), _ => Mathf.Max(0, _maxDuplicatesLevel4.Value), })); if (num <= 0) { return 0; } int num2 = Mathf.Max(2, (_fullDirectorStartsAtWaveSize != null) ? _fullDirectorStartsAtWaveSize.Value : 4); if (waveCount < num2) { num = Mathf.Min(num, Mathf.Max(0, (_mediumWaveMaxDuplicates == null) ? 1 : _mediumWaveMaxDuplicates.Value)); } return num; } private float GetWaveDuplicateChance(int level) { if (_meter < _minimumMeterForBonusSpawns.Value) { return 0f; } if (level <= 1) { return Mathf.Lerp(0.08f, 0.24f, Mathf.InverseLerp(_minimumMeterForBonusSpawns.Value, 100f, _meter)) * Mathf.Max(0f, _spawnChanceMultiplier.Value); } return level switch { 2 => 0.55f * Mathf.Max(0f, _spawnChanceMultiplier.Value), 3 => 0.7f * Mathf.Max(0f, _spawnChanceMultiplier.Value), _ => 0.8f * Mathf.Max(0f, _spawnChanceMultiplier.Value), }; } private float GetAdditionalDuplicateChance(int level, int alreadyPlanned) { if (alreadyPlanned <= 0) { return GetWaveDuplicateChance(level); } if (level <= 1) { return 0f; } return level switch { 2 => 0.28f, 3 => 0.36f, _ => 0.42f, }; } private void ApplyStartingMeterFloor() { if (_startingLevelMode != null) { float num = 0f; num = _startingLevelMode.Value switch { StartingLevelMode.Enraged => 100f, StartingLevelMode.Radiant => 200f, StartingLevelMode.LevelDeath => 300f, StartingLevelMode.Custom => Mathf.Clamp((_startingMeterPercent != null) ? _startingMeterPercent.Value : 0f, 0f, 400f), _ => 0f, }; if (num > _meter) { _meter = num; } } } private string ReadEnemyTypeName(object enemyObject, GameObject gameObject) { Type type = enemyObject.GetType(); string text = ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "FullName") ?? ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "fullName") ?? ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "enemyName") ?? ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "EnemyName"); if (!string.IsNullOrEmpty(text) && !string.Equals(text, "None", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Unknown Entity", StringComparison.OrdinalIgnoreCase)) { return text; } FieldInfo fieldInfo = type.GetField("enemyType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("type", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null) { object value = fieldInfo.GetValue(enemyObject); if (value != null) { string knownEnemyTypeAlias = EnemyCatalog.GetKnownEnemyTypeAlias(value); if (!string.IsNullOrEmpty(knownEnemyTypeAlias)) { return knownEnemyTypeAlias + " " + value; } return value.ToString(); } } PropertyInfo propertyInfo = type.GetProperty("enemyType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("type", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.GetIndexParameters().Length == 0) { object value2 = propertyInfo.GetValue(enemyObject, null); if (value2 != null) { string knownEnemyTypeAlias2 = EnemyCatalog.GetKnownEnemyTypeAlias(value2); if (!string.IsNullOrEmpty(knownEnemyTypeAlias2)) { return knownEnemyTypeAlias2 + " " + value2; } return value2.ToString(); } } return ((Object)gameObject).name ?? "unknown"; } private void RegisterLimitedTemplate(string key, GameObject source, int cost) { if (!string.IsNullOrEmpty(key) && !((Object)(object)source == (Object)null) && !IsHardBlockedTemplateName(key + " " + ((Object)source).name)) { if (!_limitedEnemyPools.TryGetValue(key, out var value)) { value = new EnemyTemplatePool(key, cost, requireLiveTemplate: true); _limitedEnemyPools[key] = value; } value.Cost = cost; value.IsLarge = EnemyCatalog.IsLargeKey(key); value.IsFlying = EnemyCatalog.IsFlyingKey(key); value.Add(source); } } private void RegisterGlobalTemplate(string key, GameObject source, int cost, bool allowInactiveTemplate) { if (!string.IsNullOrEmpty(key) && !((Object)(object)source == (Object)null) && !IsHardBlockedTemplateName(key + " " + ((Object)source).name)) { if (!_globalEnemyPools.TryGetValue(key, out var value)) { value = new EnemyTemplatePool(key, cost, !allowInactiveTemplate); _globalEnemyPools[key] = value; } value.Cost = cost; value.IsLarge = EnemyCatalog.IsLargeKey(key); value.IsFlying = EnemyCatalog.IsFlyingKey(key); value.RequireLiveTemplate = value.RequireLiveTemplate && !allowInactiveTemplate; value.Add(source); } } [IteratorStateMachine(typeof(<PrimeGlobalEnemyPoolSoon>d__159))] private IEnumerator PrimeGlobalEnemyPoolSoon() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <PrimeGlobalEnemyPoolSoon>d__159(0) { <>4__this = this }; } private void ScanGlobalEnemyPoolOncePerScene() { if (_globalPoolScannedThisScene) { return; } _globalPoolScannedThisScene = 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) { continue; } GameObject gameObject = val.gameObject; if ((Object)(object)gameObject == (Object)null || (Object)(object)gameObject.GetComponent<LevelDeathSpawnedMarker>() != (Object)null || IsHardBlockedTemplateName(((Object)gameObject).name) || (((Object)gameObject).name ?? string.Empty).IndexOf(" [Level Death]", StringComparison.OrdinalIgnoreCase) >= 0) { continue; } string text = ReadEnemyTypeName(val, gameObject); EnemyProfile profile = EnemyCatalog.GetProfile(text, ((Object)gameObject).name, _allowUnknownEnemyTypes.Value); if (!profile.Allowed) { LogUnknownOrBlocked(text, ((Object)gameObject).name, profile.BlockReason); } else if (IsTemplateSafeToSpawn(gameObject, profile.Key)) { int num2 = (_globalEnemyPools.ContainsKey(profile.Key) ? _globalEnemyPools[profile.Key].Count : 0); bool allowInactiveTemplate = !gameObject.activeInHierarchy; RegisterGlobalTemplate(profile.Key, gameObject, profile.Cost, allowInactiveTemplate); if ((_globalEnemyPools.ContainsKey(profile.Key) ? _globalEnemyPools[profile.Key].Count : 0) > num2) { num++; } } } if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death global scan added " + num + " template(s). Global enemy types available: " + _globalEnemyPools.Count + ".")); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death global enemy scan failed safely: " + ex.GetType().Name + ": " + ex.Message)); } } private bool CanSpawnInCurrentScene() { if (Time.time - _sceneLoadedTime < _minimumSceneAgeForSpawns.Value) { return false; } if (!_cyberGrindOnly.Value) { return true; } string text = Normalize(_currentSceneName); if (!text.Contains("cyber") && !text.Contains("endless")) { return text.Contains("grid"); } return true; } private float GetNaturalEnrageChance() { if (_meter < 100f) { return 0f; } if (_meter < 200f) { float num = Mathf.InverseLerp(100f, 200f, _meter); return Mathf.Lerp(Mathf.Clamp01(_naturalEnrageChanceLevel2.Value), Mathf.Clamp01(_naturalEnrageChanceLevel2.Value) * 1.35f, num); } if (_meter < 300f) { return Mathf.Clamp01(_naturalEnrageChanceLevel2.Value * 0.85f); } return Mathf.Clamp01(_naturalEnrageChanceLevel2.Value * 1.1f); } private float GetNaturalRadiantChance() { if (_meter < 200f) { return 0f; } if (_meter < 300f) { float num = Mathf.InverseLerp(200f, 400f, _meter); return Mathf.Lerp(Mathf.Clamp01(_naturalRadiantChanceLevel3.Value), Mathf.Clamp01(_naturalRadiantChanceLevel3.Value) * 1.35f, num); } return Mathf.Clamp01(_naturalRadiantChanceLevel3.Value * 1.65f); } private float GetTypeReplacementChance() { if (_directorActionMode.Value == DirectorActionMode.AddReinforcementsOnly) { return 0f; } if (_meter < 100f) { return 0f; } float num = ((_meter < 200f) ? Mathf.Lerp(0.08f, Mathf.Clamp01(_replacementChanceLevel2.Value), Mathf.InverseLerp(100f, 200f, _meter)) : ((!(_meter < 300f)) ? Mathf.Lerp(Mathf.Clamp01(_replacementChanceLevel3.Value), Mathf.Clamp01(_replacementChanceLevel4.Value), Mathf.InverseLerp(300f, 400f, _meter)) : Mathf.Lerp(Mathf.Clamp01(_replacementChanceLevel2.Value), Mathf.Clamp01(_replacementChanceLevel3.Value), Mathf.InverseLerp(200f, 300f, _meter)))); if (_directorActionMode.Value == DirectorActionMode.PreferUpgrades) { num *= 1.2f; } return Mathf.Clamp01(num); } private EnemySpawnCandidate ChooseReplacementCandidate(EnemyProfile sourceProfile) { return ChooseReplacementCandidate(sourceProfile, null); } private EnemySpawnCandidate ChooseReplacementCandidate(EnemyProfile sourceProfile, GameObject source) { CleanupPools(); int pointCapForSource = GetPointCapForSource(sourceProfile.Cost); int num = Mathf.Max(sourceProfile.Cost + 1, 3); int num2 = Mathf.Max(num, pointCapForSource); List<EnemySpawnCandidate> list = new List<EnemySpawnCandidate>(); HashSet<string> hashSet = new HashSet<string>(_limitedEnemyPools.Keys); foreach (EnemyTemplatePool value in _globalEnemyPools.Values) { if (!value.HasUsableTemplate || value.Cost < num || (hashSet.Contains(value.Key) && value.Cost <= sourceProfile.Cost) || ShouldSuppressLowTier(value.Key)) { continue; } int num3 = (hashSet.Contains(value.Key) ? value.Cost : GetGlobalEffectiveCost(value.Cost)); if (num3 <= num2) { EnemySpawnCandidate enemySpawnCandidate = new EnemySpawnCandidate(value, num3, !hashSet.Contains(value.Key)); if (CanCandidateReplaceSource(enemySpawnCandidate, sourceProfile, source)) { list.Add(enemySpawnCandidate); } } } if (list.Count == 0) { return null; } List<WeightedCandidate> list2 = new List<WeightedCandidate>(); foreach (EnemySpawnCandidate item in list) { float num4 = Mathf.Max(1f, (float)(item.BaseCost - sourceProfile.Cost)); float num5 = 0.75f + num4 * 0.22f; if (item.BaseCost >= 8 && _meter < 200f) { num5 *= 0.35f; } if (item.BaseCost >= 9 && _meter < 260f) { num5 *= 0.45f; } if (item.BaseCost >= 10 && _meter < 320f) { num5 *= 0.35f; } if (item.IsGlobalOnly) { num5 *= 0.9f; } if (item.IsLarge) { num5 *= Mathf.Clamp((_largeEnemyRarityMultiplier != null) ? _largeEnemyRarityMultiplier.Value : 0.5f, 0.05f, 1f); } num5 *= GetEnemyVarietyCooldownWeight(item.Pool.Key, item.BaseCost, item.IsLarge); list2.Add(new WeightedCandidate(item, num5)); } return PickWeightedCandidate(list2); } private float GetEnemyVarietyCooldownWeight(string key, int cost, bool isLarge) { if (_enableEnemyVarietyCooldown == null || !_enableEnemyVarietyCooldown.Value) { return 1f; } if (((_enemyVarietyCooldownStrength != null) ? _enemyVarietyCooldownStrength.Value : 1f) <= 0f) { return 1f; } if (cost <= 6) { return 1f; } key = Normalize(key); if (!_enemyCooldowns.TryGetValue(key, out var value)) { return 1f; } DecayEnemyCooldown(value); if (value.Heat <= 0.01f) { return 1f; } float num = Mathf.Max(0f, (_enemyVarietyCooldownStrength != null) ? _enemyVarietyCooldownStrength.Value : 1f); float num2 = 1f + (float)Mathf.Max(0, cost - 6) * 0.35f; if (isLarge) { num2 += Mathf.Max(0f, (_largeEnemyCooldownBonus != null) ? _largeEnemyCooldownBonus.Value : 3.5f) * 0.35f; } return Mathf.Clamp(1f / (1f + value.Heat * num2 * num), 0.04f, 1f); } private void RecordEnemySelectionCooldown(string key, int cost, bool isLarge) { if (_enableEnemyVarietyCooldown == null || !_enableEnemyVarietyCooldown.Value || cost <= 6) { return; } key = Normalize(key); if (!string.IsNullOrEmpty(key)) { if (!_enemyCooldowns.TryGetValue(key, out var value)) { value = new EnemyCooldownState(); value.LastUpdatedTime = Time.time; _enemyCooldowns[key] = value; } DecayEnemyCooldown(value); float num = Mathf.Max(0.5f, (float)(cost - 6) * 0.85f); if (isLarge) { num += Mathf.Max(0f, (_largeEnemyCooldownBonus != null) ? _largeEnemyCooldownBonus.Value : 3.5f); } value.Heat = Mathf.Clamp(value.Heat + num, 0f, 20f); value.LastUpdatedTime = Time.time; } } private void DecayEnemyCooldown(EnemyCooldownState state) { if (state != null) { float time = Time.time; float num = Mathf.Max(0f, time - state.LastUpdatedTime); float num2 = Mathf.Max(1f, (_enemyVarietyCooldownDecaySeconds != null) ? _enemyVarietyCooldownDecaySeconds.Value : 22f); if (num > 0f) { state.Heat *= Mathf.Exp((0f - num) / num2); state.LastUpdatedTime = time; } } } private EnemySpawnCandidate PickWeightedCandidate(List<WeightedCandidate> weighted) { if (weighted == null || weighted.Count == 0) { return null; } float num = 0f; for (int i = 0; i < weighted.Count; i++) { num += weighted[i].Weight; } 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 CanCandidateReplaceSource(EnemySpawnCandidate candidate, EnemyProfile sourceProfile, GameObject source) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (candidate == null || candidate.Pool == null) { return false; } if (!candidate.IsLarge) { return true; } if (_enableLargeEnemyPool == null || !_enableLargeEnemyPool.Value) { return false; } if (sourceProfile.IsFlying) { return false; } if ((Object)(object)source == (Object)null) { return false; } int num = Mathf.Max(1, (_largeEnemyMinNonFilthWaveSize != null) ? _largeEnemyMinNonFilthWaveSize.Value : 5); if (_activeWaveNonFilthEnemyCount < num) { return false; } if (!HasGroundBelow(source.transform.position)) { return false; } return true; } private bool HasGroundBelow(Vector3 position) { //IL_0025: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(1f, (_largeEnemyGroundCheckDistance != null) ? _largeEnemyGroundCheckDistance.Value : 8f); RaycastHit val = default(RaycastHit); return Physics.Raycast(position + Vector3.up * 1.25f, Vector3.down, ref val, num + 1.25f, -1, (QueryTriggerInteraction)1); } private bool SpawnReplacement(PendingWaveEnemy entry, EnemyTemplatePool pool, EnemyProfile sourceProfile, EnemySpawnCandidate candidate) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) GameObject val = entry?.Source; bool flag = IsEntrySourceAlive(entry); GameObject randomLiveTemplate = pool.GetRandomLiveTemplate(); if ((Object)(object)randomLiveTemplate == (Object)null) { return false; } if (!IsTemplateSafeToSpawn(randomLiveTemplate, pool.Key)) { return false; } try { Vector3 val2 = (flag ? val.transform.position : (entry?.Position ?? Vector3.zero)); if (candidate != null && candidate.IsLarge && (!flag || !CanCandidateReplaceSource(candidate, sourceProfile, val))) { return false; } Quaternion val3 = (flag ? val.transform.rotation : (entry?.Rotation ?? Quaternion.identity)); Transform val4 = (flag ? val.transform.parent : entry?.Parent); GameObject val5 = Object.Instantiate<GameObject>(randomLiveTemplate, val2, val3, val4); ((Object)val5).name = ((Object)randomLiveTemplate).name + " [Level Death Replacement]"; val5.AddComponent<LevelDeathSpawnedMarker>(); SanitizeClonedEnemyRuntimeState(val5); if (!val5.activeSelf) { val5.SetActive(true); } if (!IsSpawnedCloneValid(val5, pool.Key)) { DestroyCloneIfPresent(val5); if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death rejected a replacement clone that did not validate: " + pool.Key)); } return false; } TryApplyHighMeterSpawnBuffs(val5); RecordEnemySelectionCooldown(pool.Key, pool.Cost, pool.IsLarge); TrySpawnCerberusPairBonus(val5, val2, val4, (candidate != null) ? candidate.Pool : pool); if (candidate != null && candidate.IsLarge) { _activeWaveLargeEnemySpawned = true; } if (flag) { ((MonoBehaviour)this).StartCoroutine(DestroyOriginalAfterReplacementValid(val, val5, pool.Key)); } _nextAllowedBonusSpawnTime = Time.time + Mathf.Max(0.1f, _minimumSecondsBetweenBonusSpawns.Value * 0.75f); if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death replaced natural " + sourceProfile.Key + " with " + pool.Key + " at " + Mathf.RoundToInt(_meter) + "%, effectiveCost=" + (candidate?.EffectiveCost ?? pool.Cost) + ".")); } return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death skipped a type replacement safely: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private int GetPointCapForSource(int baseCost) { return Mathf.Max(baseCost, baseCost + GetBonusBudgetForMeter()); } private int GetBonusBudgetForMeter() { float num = Mathf.Clamp(_meter, 0f, 400f); int num2 = Mathf.Max(1, _maxBonusBudget.Value); int num3 = Mathf.Max(num2, Mathf.RoundToInt((float)num2 * Mathf.Max(1f, _deathMarkBudgetMultiplier.Value))); int num4 = Mathf.Max(num3, _maxOvercapBonusBudget.Value); float num5 = Mathf.InverseLerp(_minimumMeterForBonusSpawns.Value, 100f, Mathf.Min(num, 100f)); if (num < 100f) { return Mathf.RoundToInt(Mathf.Lerp(0f, (float)num2, num5)); } float num6 = Mathf.InverseLerp(100f, 400f, num); return Mathf.RoundToInt(Mathf.Lerp((float)num3, (float)num4, Mathf.Pow(Mathf.Clamp01(num6), 0.82f))); } private bool ShouldSuppressLowTier(string key) { return false; } private int GetGlobalEffectiveCost(int baseCost) { float num = Mathf.Max(1f, _globalEnemyCostMultiplier.Value); int num2 = Mathf.Max(0, _globalEnemyFlatSurcharge.Value); return Mathf.Max(baseCost + 1, Mathf.CeilToInt((float)baseCost * num) + num2); } private bool ShouldAvoidCloningOrReplacingSource(GameObject source) { if ((Object)(object)source == (Object)null || _sanitizeInheritedIdolState == null || !_sanitizeInheritedIdolState.Value) { return false; } try { foreach (Component item in FindEnemyIdentifierComponents(source)) { if ((Object)(object)item == (Object)null) { continue; } Type type = ((object)item).GetType(); if (ReadBoolMember(type, item, "blessed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death skipped an Idol-blessed enemy as a clone/replacement source: " + ((Object)source).name)); } return true; } } } catch (Exception ex) { if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death could not inspect Idol/blessed state on " + ((Object)source).name + ": " + ex.Message)); } } return false; } private void SanitizeClonedEnemyRuntimeState(GameObject clone) { if ((Object)(object)clone == (Object)null || _sanitizeInheritedIdolState == null || !_sanitizeInheritedIdolState.Value) { return; } try { bool flag = false; foreach (Component item in FindEnemyIdentifierComponents(clone)) { if (!((Object)(object)item == (Object)null)) { Type type = ((object)item).GetType(); if (ReadBoolMember(type, item, "blessed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { SetBoolMember(type, item, "blessed", value: false, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); flag = true; } InvokeMethodIfExists(type, item, "UpdateBuffs", null); InvokeMethodIfExists(type, item, "UpdateModifiers", null); } } if (flag && _debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death cleared inherited Idol/blessed state from clone: " + ((Object)clone).name)); } } catch (Exception ex) { if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death could not sanitize clone runtime state on " + ((Object)clone).name + ": " + ex.Message)); } } } [IteratorStateMachine(typeof(<FindEnemyIdentifierComponents>d__180))] private IEnumerable<Component> FindEnemyIdentifierComponents(GameObject gameObject) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FindEnemyIdentifierComponents>d__180(-2) { <>4__this = this, <>3__gameObject = gameObject }; } private void TryApplyHighMeterSpawnBuffs(GameObject clone) { if (!_allowHighMeterEnemyBuffs.Value || (Object)(object)clone == (Object)null || _meter < 100f) { return; } try { bool flag = RollRadiant(); bool flag2 = RollEnrage(); if (flag) { ApplyRadiantLikeBuff(clone); } if (flag2) { ApplyEnrage(clone); } if (_debugLogging.Value && (flag || flag2)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death buffed reinforcement " + ((Object)clone).name + " radiant=" + flag + " enraged=" + flag2 + " meter=" + Mathf.RoundToInt(_meter) + "%.")); } } catch (Exception ex) { if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death skipped high-meter spawn buff safely: " + ex.GetType().Name + ": " + ex.Message)); } } } private bool RollEnrage() { if (_meter < 100f) { return false; } float num = Mathf.InverseLerp(100f, 400f, _meter); float num2 = Mathf.Lerp(Mathf.Clamp01(_enrageChanceAt300.Value), Mathf.Clamp01(_enrageChanceAt1000.Value), Mathf.Sqrt(Mathf.Clamp01(num))); return Random.value < num2; } private bool RollRadiant() { if (_meter < 200f) { return false; } float num = Mathf.InverseLerp(200f, 400f, _meter); float num2 = Mathf.Lerp(Mathf.Clamp01(_radiantChanceAt400.Value), Mathf.Clamp01(_radiantChanceAt1000.Value), Mathf.Sqrt(Mathf.Clamp01(num))); return Random.value < num2; } private void ApplyRadiantLikeBuff(GameObject clone) { Component val = FindEnemyIdentifierComponent(clone); if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); float num = Mathf.Clamp(_radianceTier.Value, 1f, 3f); SetFloatMember(type, val, "radianceTier", num, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); InvokeMethodIfExists(type, val, "SpeedBuff", new object[1] { num }); InvokeMethodIfExists(type, val, "HealthBuff", new object[1] { num }); InvokeMethodIfExists(type, val, "DamageBuff", null); InvokeMethodIfExists(type, val, "UpdateBuffs", null); InvokeMethodIfExists(type, val, "UpdateModifiers", null); InvokeMethodIfExists(type, val, "ForceGetHealth", null); } } private void ApplyEnrage(GameObject clone) { Component[] componentsInChildren = clone.GetComponentsInChildren<Component>(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) { return false; } if (AccessTools.Method(type, "Enrage", (Type[])null, (Type[])null) == null) { return false; } if (type.Name.IndexOf("enrage", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } Type[] interfaces = type.GetInterfaces(); foreach (Type type2 in interfaces) { if (type2.Name.IndexOf("IEnrage", StringComparison.OrdinalIgnoreCase) >= 0 || type2.Name.IndexOf("Enrage", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } 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 void SetFloatMember(Type type, object instance, string memberName, float value, BindingFlags flags) { FieldInfo field = type.GetField(memberName, flags); if (field != null && field.FieldType == typeof(float)) { field.SetValue(instance, value); return; } PropertyInfo property = type.GetProperty(memberName, flags); if (property != null && property.PropertyType == typeof(float) && property.CanWrite) { property.SetValue(instance, value, null); } } private string ReadStringMember(object instance, Type type, BindingFlags flags, string memberName) { if (instance == null || type == null || string.IsNullOrEmpty(memberName)) { return null; } try { FieldInfo field = type.GetField(memberName, flags); if (field != null) { object value = field.GetValue(instance); if (value != null) { return value.ToString(); } } PropertyInfo property = type.GetProperty(memberName, flags); if (property != null && property.GetIndexParameters().Length == 0 && property.CanRead) { object value2 = property.GetValue(instance, null); if (value2 != null) { return value2.ToString(); } } } catch { } return null; } private bool ReadBoolMember(Type type, object instance, string memberName, BindingFlags flags) { FieldInfo field = type.GetField(memberName, flags); if (field != null && field.FieldType == typeof(bool)) { return (bool)field.GetValue(instance); } PropertyInfo property = type.GetProperty(memberName, flags); if (property != null && property.PropertyType == typeof(bool) && property.CanRead) { return (bool)property.GetValue(instance, null); } return false; } private void SetBoolMember(Type type, object instance, string memberName, bool value, BindingFlags flags) { FieldInfo field = type.GetField(memberName, flags); if (field != null && field.FieldType == typeof(bool)) { field.SetValue(instance, value); return; } PropertyInfo property = type.GetProperty(memberName, flags); if (property != null && property.PropertyType == typeof(bool) && property.CanWrite) { property.SetValue(instance, value, null); } } private void InvokeMethodIfExists(Type type, object instance, string methodName, object[] args) { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (!(methodInfo == null)) { ParameterInfo[] parameters = methodInfo.GetParameters(); object[] parameters2 = args; if (parameters.Length == 0) { parameters2 = null; } else if (args == null || args.Length != parameters.Length) { return; } methodInfo.Invoke(instance, parameters2); } } private Vector3 PickSpawnPosition(Vector3 sourcePosition, int index) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (mig