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