using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.LowLevel; using UnityEngine.PlayerLoop; 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: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AutoDefense { internal sealed class Hardkill { private bool _restorePending; private byte _savedStation; private readonly List _savedTargets = new List(); private float _lastFire = -999f; public string Status { get; private set; } = "idle"; public void Reset() { _restorePending = false; _savedTargets.Clear(); Status = "idle"; } public void Tick(Aircraft aircraft, ThreatTable threats) { if (_restorePending) { Restore(aircraft); return; } if (!Plugin.HardkillEnabled.Value) { Status = "off"; return; } float timeSinceLevelLoad = Time.timeSinceLevelLoad; if (timeSinceLevelLoad - _lastFire < Plugin.HardkillCooldown.Value) { Status = "cooldown"; return; } Threat threat = SelectTarget(threats, timeSinceLevelLoad); if (threat == null) { Status = "no shot"; return; } WeaponStation val = SelectStation(aircraft, threat); if (val == null) { Status = "no station"; } else { Fire(aircraft, threat, val, timeSinceLevelLoad); } } private Threat SelectTarget(ThreatTable threats, float now) { Threat result = null; float num = float.MaxValue; for (int i = 0; i < threats.Active.Count; i++) { Threat threat = threats.Active[i]; if (threat.InterceptorsFired < Plugin.HardkillMaxPerThreat.Value && !(threat.Tti < Plugin.HardkillMinTti.Value) && !(threat.Tti > Plugin.HardkillMaxTti.Value)) { float num2 = (threat.Counterable ? (threat.Tti + 3f) : threat.Tti); if (num2 < num) { num = num2; result = threat; } } } return result; } private WeaponStation SelectStation(Aircraft aircraft, Threat t) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) WeaponStation val = null; float num = float.MaxValue; List weaponStations = ((Unit)aircraft).weaponStations; if (weaponStations == null) { return null; } Vector3 val2 = ((Component)t.Missile).transform.position - ((Component)aircraft).transform.position; for (int i = 0; i < weaponStations.Count; i++) { WeaponStation val3 = weaponStations[i]; if (val3 == null || val3.Cargo || val3.SalvoInProgress) { continue; } WeaponInfo weaponInfo = val3.WeaponInfo; if ((Object)(object)weaponInfo == (Object)null || weaponInfo.gun || val3.Ammo <= Plugin.HardkillAmmoReserve.Value) { continue; } try { if (!val3.Ready() || val3.SafetyIsOn(aircraft)) { continue; } goto IL_00bf; } catch { } continue; IL_015f: float costPerRound = weaponInfo.costPerRound; if (val == null || costPerRound < num) { num = costPerRound; val = val3; } continue; IL_00bf: TargetRequirements targetRequirements = weaponInfo.targetRequirements; if ((targetRequirements.maxRange > 0f && t.Range > targetRequirements.maxRange) || t.Range < targetRequirements.minRange || (targetRequirements.minAlignment > 0f && Vector3.Angle(val2, ((Component)aircraft).transform.forward) > targetRequirements.minAlignment)) { continue; } try { if ((targetRequirements.minIR > 0f && !((Unit)t.Missile).HasIRSignature()) || (targetRequirements.minRadar > 0f && !((Unit)t.Missile).HasRadarEmission())) { continue; } goto IL_015f; } catch { goto IL_015f; } } return val; } private void Fire(Aircraft aircraft, Threat t, WeaponStation station, float now) { WeaponManager weaponManager = aircraft.weaponManager; if ((Object)(object)weaponManager == (Object)null) { Status = "no weapon manager"; return; } try { _savedStation = ((weaponManager.currentWeaponStation != null) ? weaponManager.currentWeaponStation.Number : station.Number); _savedTargets.Clear(); List targetList = weaponManager.GetTargetList(); if (targetList != null) { _savedTargets.AddRange(targetList); } weaponManager.SetActiveStation(station.Number); weaponManager.ClearTargetList(); weaponManager.AddTargetList((Unit)(object)t.Missile); weaponManager.Fire(); } catch { Status = "fire failed"; _restorePending = true; return; } t.InterceptorsFired++; _lastFire = now; _restorePending = true; Status = station.WeaponInfo.shortName + " at " + t.Name; Plugin.LogEngagement($"hardkill {t.Name} {t.SeekerType} tti={t.Tti:F2} range={t.Range:F0} " + $"station={station.Number} weapon={station.WeaponInfo.shortName} ammo={station.Ammo}"); Plugin.Notify("INTERCEPT: " + station.WeaponInfo.shortName + " — " + t.Name); } private void Restore(Aircraft aircraft) { _restorePending = false; WeaponManager weaponManager = aircraft.weaponManager; if ((Object)(object)weaponManager == (Object)null) { return; } try { weaponManager.SetActiveStation(_savedStation); weaponManager.ClearTargetList(); for (int num = _savedTargets.Count - 1; num >= 0; num--) { if ((Object)(object)_savedTargets[num] != (Object)null) { weaponManager.AddTargetList(_savedTargets[num]); } } } catch { } _savedTargets.Clear(); } } [BepInPlugin("hendev.nuclearoption.autodefense", "AutoDefense", "1.0.1")] public class Plugin : BaseUnityPlugin { [StructLayout(LayoutKind.Sequential, Size = 1)] private struct AdFixedUpdate { } [StructLayout(LayoutKind.Sequential, Size = 1)] private struct AdUpdate { } public const string Guid = "hendev.nuclearoption.autodefense"; public const string Name = "AutoDefense"; public const string Version = "1.0.1"; private static ManualLogSource _log; internal static ConfigEntry MasterEnabled; internal static ConfigEntry SoftkillEnabled; internal static ConfigEntry RequireActiveLock; internal static ConfigEntry IrLead; internal static ConfigEntry RadarLead; internal static ConfigEntry MinLead; internal static ConfigEntry LeadMargin; internal static ConfigEntry DefaultShots; internal static ConfigEntry MaxPulsesPerBurst; internal static ConfigEntry MaxBursts; internal static ConfigEntry BurstGap; internal static ConfigEntry AmmoReserve; internal static ConfigEntry ReserveOverrideTti; internal static ConfigEntry TriggerRefresh; internal static ConfigEntry HardkillEnabled; internal static ConfigEntry HardkillMinTti; internal static ConfigEntry HardkillMaxTti; internal static ConfigEntry HardkillMaxPerThreat; internal static ConfigEntry HardkillAmmoReserve; internal static ConfigEntry HardkillCooldown; internal static ConfigEntry StateFile; internal static ConfigEntry EngagementLog; internal static ConfigEntry HudMessages; internal static ConfigEntry ReviveConfigWindow; private static string _statePath; private static string _logPath; private readonly ThreatTable _threats = new ThreatTable(); private readonly Softkill _softkill = new Softkill(); private readonly Hardkill _hardkill = new Hardkill(); private Aircraft _aircraft; private float _lastStateWrite; private static float _lastNotify; private GameObject _revivedHost; private readonly HashSet _revivedTypes = new HashSet(); private void Awake() { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Expected O, but got Unknown //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Expected O, but got Unknown //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Expected O, but got Unknown //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Expected O, but got Unknown //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Expected O, but got Unknown //IL_032c: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Expected O, but got Unknown //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Expected O, but got Unknown //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Expected O, but got Unknown //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Expected O, but got Unknown //IL_0409: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Expected O, but got Unknown _log = ((BaseUnityPlugin)this).Logger; MasterEnabled = ((BaseUnityPlugin)this).Config.Bind("Master", "Enabled", true, "Master switch for the whole mod. Off = the game behaves exactly as stock."); SoftkillEnabled = ((BaseUnityPlugin)this).Config.Bind("Softkill", "Enabled", true, "Automatically deploy flares/chaff against missiles that are tracking you."); RequireActiveLock = ((BaseUnityPlugin)this).Config.Bind("Softkill", "RequireActiveLock", true, "Only spend chaff once a radar missile has gone active (seekerMode = activeLock). ARH/SARH seekers discard chaff entirely before that point, so turning this off just throws ammo away during mid-course."); IrLead = ((BaseUnityPlugin)this).Config.Bind("Softkill", "IrLeadSeconds", 5f, new ConfigDescription("Furthest ahead of impact flares will ever be released. The mod normally fires much later than this — only as early as the modelled salvo needs — because the IR seeker gets easier to dazzle as it closes.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 20f), Array.Empty())); RadarLead = ((BaseUnityPlugin)this).Config.Bind("Softkill", "RadarLeadSeconds", 6f, new ConfigDescription("Same, for chaff. Chaff gain scales with (1 - range/maxRange), so late is strictly better.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 20f), Array.Empty())); MinLead = ((BaseUnityPlugin)this).Config.Bind("Softkill", "MinLeadSeconds", 0.9f, new ConfigDescription("Never open fire later than this before impact.", (AcceptableValueBase)(object)new AcceptableValueRange(0.2f, 5f), Array.Empty())); LeadMargin = ((BaseUnityPlugin)this).Config.Bind("Softkill", "LeadMarginSeconds", 0.4f, new ConfigDescription("Slack added to the modelled salvo time, to absorb network delay and a wrong model.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); DefaultShots = ((BaseUnityPlugin)this).Config.Bind("Softkill", "FallbackDecoysPerBurst", 6, new ConfigDescription("Decoys per burst when the seeker model is unavailable.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 30), Array.Empty())); MaxPulsesPerBurst = ((BaseUnityPlugin)this).Config.Bind("Softkill", "MaxPulsesPerBurst", 4, new ConfigDescription("Upper bound on ejector cycles in a single burst.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); MaxBursts = ((BaseUnityPlugin)this).Config.Bind("Softkill", "MaxBurstsPerThreat", 8, new ConfigDescription("Give up on a single missile after this many bursts.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), Array.Empty())); BurstGap = ((BaseUnityPlugin)this).Config.Bind("Softkill", "BurstGapSeconds", 0.35f, new ConfigDescription("Pause between bursts, so the seeker has a chance to drop lock.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 3f), Array.Empty())); AmmoReserve = ((BaseUnityPlugin)this).Config.Bind("Softkill", "AmmoReserve", 0, new ConfigDescription("Keep this many decoys back for manual use.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); ReserveOverrideTti = ((BaseUnityPlugin)this).Config.Bind("Softkill", "ReserveOverrideTti", 2.5f, new ConfigDescription("Below this time to impact the reserve is ignored — a saved flare is worth nothing after the hit.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); TriggerRefresh = ((BaseUnityPlugin)this).Config.Bind("Softkill", "TriggerRefreshSeconds", 0.25f, new ConfigDescription("How often to re-assert the game's networked countermeasure trigger during a burst. PilotPlayerState clears it every frame you are not holding the countermeasure button, and the trigger is what makes other clients deploy for you in multiplayer. Local ejection does not depend on it.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 2f), Array.Empty())); HardkillEnabled = ((BaseUnityPlugin)this).Config.Bind("Hardkill", "Enabled", true, "Fire an interceptor at the incoming missile. This briefly takes over your weapon station and target list (both are restored on the next physics step) and is the only answer to seekers no decoy affects (ARM, optical, laser). Experimental — turn it off if it misbehaves."); HardkillMinTti = ((BaseUnityPlugin)this).Config.Bind("Hardkill", "MinTti", 2.5f, new ConfigDescription("Do not bother below this time to impact.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 20f), Array.Empty())); HardkillMaxTti = ((BaseUnityPlugin)this).Config.Bind("Hardkill", "MaxTti", 15f, new ConfigDescription("Do not shoot at threats further out than this in time.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 60f), Array.Empty())); HardkillMaxPerThreat = ((BaseUnityPlugin)this).Config.Bind("Hardkill", "MaxInterceptorsPerThreat", 1, new ConfigDescription("Rounds spent on any one incoming missile.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 5), Array.Empty())); HardkillAmmoReserve = ((BaseUnityPlugin)this).Config.Bind("Hardkill", "StationAmmoReserve", 0, new ConfigDescription("Never draw a station below this many rounds for interception.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); HardkillCooldown = ((BaseUnityPlugin)this).Config.Bind("Hardkill", "CooldownSeconds", 1.5f, new ConfigDescription("Minimum spacing between interceptor launches.", (AcceptableValueBase)(object)new AcceptableValueRange(0.2f, 10f), Array.Empty())); StateFile = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "StateFile", true, "Write BepInEx/AutoDefense.state.txt once a second with the live threat picture."); EngagementLog = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "EngagementLog", true, "Append every decision and outcome to BepInEx/AutoDefense.engagements.log."); HudMessages = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "HudMessages", true, "Brief on-screen note when the mod acts."); ReviveConfigWindow = ((BaseUnityPlugin)this).Config.Bind("Compatibility", "ReviveConfigWindow", true, "Keep BepInEx plugin hosts alive so the ConfigurationManager settings window works in this game, which otherwise destroys them and leaves IMGUI windows dead. Harmless when another mod already does this — whichever gets there first wins. Turn off if you would rather edit the config file and have this mod touch nothing else."); try { _statePath = Path.Combine(Paths.BepInExRootPath, "AutoDefense.state.txt"); _logPath = Path.Combine(Paths.BepInExRootPath, "AutoDefense.engagements.log"); } catch (Exception) { } SceneManager.sceneLoaded += delegate { InstallPlayerLoop(); }; InstallPlayerLoop(); _log.LogInfo((object)"AutoDefense 1.0.1 loaded"); LogEngagement("=== AutoDefense 1.0.1 loaded ==="); } private void InstallPlayerLoop() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) PlayerLoopSystem root = PlayerLoop.GetCurrentPlayerLoop(); if ((0u | (AddTick(ref root, typeof(FixedUpdate), typeof(AdFixedUpdate), new UpdateFunction(TickFixed)) ? 1u : 0u) | (AddTick(ref root, typeof(Update), typeof(AdUpdate), new UpdateFunction(TickUpdate)) ? 1u : 0u)) != 0) { PlayerLoop.SetPlayerLoop(root); } } private static bool AddTick(ref PlayerLoopSystem root, Type phaseType, Type markerType, UpdateFunction cb) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) PlayerLoopSystem[] subSystemList = root.subSystemList; for (int i = 0; i < subSystemList.Length; i++) { if (subSystemList[i].type != phaseType) { continue; } PlayerLoopSystem[] array = subSystemList[i].subSystemList ?? Array.Empty(); PlayerLoopSystem[] array2 = array; for (int j = 0; j < array2.Length; j++) { if (array2[j].type == markerType) { return false; } } List list = new List(array) { new PlayerLoopSystem { type = markerType, updateDelegate = cb } }; subSystemList[i].subSystemList = list.ToArray(); return true; } return false; } private void Update() { } private void FixedUpdate() { } private void KeepPluginHostAlive() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown if (this != null && (Object)(object)this != (Object)null) { if (!((Component)this).gameObject.activeSelf) { ((Component)this).gameObject.SetActive(true); } if (!((Behaviour)this).enabled) { ((Behaviour)this).enabled = true; } MonoBehaviour[] components = ((Component)this).gameObject.GetComponents(); foreach (MonoBehaviour val in components) { if ((Object)(object)val != (Object)null && !((Behaviour)val).enabled) { ((Behaviour)val).enabled = true; } } return; } if ((Object)(object)_revivedHost == (Object)null) { _revivedHost = new GameObject("AutoDefense_PluginHost") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)_revivedHost); _revivedTypes.Clear(); } foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { if (pluginInfo.Key == "hendev.nuclearoption.autodefense") { continue; } PluginInfo value = pluginInfo.Value; BaseUnityPlugin val2 = ((value != null) ? value.Instance : null); if (val2 == null || (Object)(object)val2 != (Object)null) { continue; } Type type = ((object)val2).GetType(); if (!_revivedTypes.Contains(type)) { _revivedTypes.Add(type); Component value2 = _revivedHost.GetComponent(type) ?? _revivedHost.AddComponent(type); try { typeof(PluginInfo).GetProperty("Instance")?.SetValue(pluginInfo.Value, value2, null); } catch (Exception) { } } } } private void TickFixed() { try { Step(); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("tick failed: " + ex.Message)); } } } private void TickUpdate() { if (ReviveConfigWindow.Value) { try { KeepPluginHostAlive(); } catch (Exception) { } } try { WriteState(); } catch (Exception) { } } private void Step() { if (!MasterEnabled.Value) { Disengage(); return; } Aircraft val = default(Aircraft); if (!GameManager.GetLocalAircraft(ref val) || (Object)(object)val == (Object)null || ((Unit)val).disabled) { Disengage(); _aircraft = null; return; } if (!((Unit)val).LocalSim) { Disengage(); return; } if (val != _aircraft) { _threats.Clear(); _softkill.Reset(_aircraft); _hardkill.Reset(); _aircraft = val; } _threats.Refresh(val); for (int i = 0; i < _threats.Active.Count; i++) { Threat threat = _threats.Active[i]; SeekerModel.Evaluate(threat, val, threat.SelfEstimate); } List resolved = _threats.Resolved; for (int j = 0; j < resolved.Count; j++) { Threat threat2 = resolved[j]; LogEngagement($"gone {threat2.Name} {threat2.SeekerType} bursts={threat2.Salvos} decoys={threat2.DecoysSpent} " + $"interceptors={threat2.InterceptorsFired} lastTti={threat2.Tti:F1} " + $"tracked={Time.timeSinceLevelLoad - threat2.FirstSeen:F1}s"); if (threat2.Salvos > 0 || threat2.InterceptorsFired > 0) { Notify(threat2.Name + " broken"); } } _softkill.Tick(val, _threats); _hardkill.Tick(val, _threats); } private void Disengage() { _softkill.Reset(_aircraft); _hardkill.Reset(); _threats.Clear(); } private void WriteState() { if (!StateFile.Value || _statePath == null) { return; } float timeSinceLevelLoad = Time.timeSinceLevelLoad; if (timeSinceLevelLoad - _lastStateWrite < 1f) { return; } _lastStateWrite = timeSinceLevelLoad; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} {1} t={2:F1}", "AutoDefense", "1.0.1", timeSinceLevelLoad)); stringBuilder.AppendLine("aircraft=" + (((Object)(object)_aircraft != (Object)null) ? ((Unit)_aircraft).unitName : "none") + " " + $"master={MasterEnabled.Value} softkill={SoftkillEnabled.Value} hardkill={HardkillEnabled.Value}"); stringBuilder.AppendLine("softkill=" + _softkill.Status); stringBuilder.AppendLine("hardkill=" + _hardkill.Status); stringBuilder.AppendLine($"threats={_threats.Active.Count}"); for (int i = 0; i < _threats.Active.Count; i++) { Threat threat = _threats.Active[i]; stringBuilder.AppendLine(" " + threat.Name + " seeker=" + threat.SeekerType + " mode=" + (threat.ActiveLock ? "activeLock" : "passive/search") + " " + $"range={threat.Range:F0} closure={threat.Closure:F0} tti={threat.Tti:F1} lead={threat.Lead:F2} " + "need=" + ((threat.ShotsNeeded < 0) ? "model-unavailable" : threat.ShotsNeeded.ToString()) + " " + $"thr={threat.Threshold:F2} gain={threat.PerShotGain:F3} est={threat.SelfEstimate:F2} " + $"bursts={threat.Salvos} decoys={threat.DecoysSpent} interceptors={threat.InterceptorsFired}"); } try { File.WriteAllText(_statePath, stringBuilder.ToString()); } catch (Exception) { } } internal static void LogEngagement(string line) { if (EngagementLog == null || !EngagementLog.Value || _logPath == null) { return; } try { File.AppendAllText(_logPath, DateTime.Now.ToString("HH:mm:ss.fff") + " " + line + Environment.NewLine); } catch (Exception) { } } internal static void Notify(string msg) { if (HudMessages == null || !HudMessages.Value) { return; } float timeSinceLevelLoad = Time.timeSinceLevelLoad; if (timeSinceLevelLoad - _lastNotify < 0.5f) { return; } _lastNotify = timeSinceLevelLoad; try { GameplayUI i = SceneSingleton.i; if ((Object)(object)i != (Object)null) { i.GameMessage(msg); } } catch (Exception) { } } } internal static class SeekerModel { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Dictionary _fieldCache = new Dictionary(); private const float IrPerpAssumed = 0f; private const float ChaffPerpAssumed = 0.5f; private static FieldInfo Field(Type type, string name) { string key = type.FullName + "." + name; if (_fieldCache.TryGetValue(key, out var value)) { return value; } FieldInfo fieldInfo = null; Type type2 = type; while (type2 != null && fieldInfo == null) { fieldInfo = type2.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type2 = type2.BaseType; } _fieldCache[key] = fieldInfo; return fieldInfo; } public static bool TryGet(object obj, string name, out T value) { value = default(T); if (obj == null) { return false; } try { FieldInfo fieldInfo = Field(obj.GetType(), name); if (fieldInfo == null || !typeof(T).IsAssignableFrom(fieldInfo.FieldType)) { return false; } value = (T)fieldInfo.GetValue(obj); return true; } catch { return false; } } public static IRSource OwnIRSource(Unit unit) { if (!TryGet>(unit, "IRSources", out var value) || value == null) { return null; } for (int i = 0; i < value.Count; i++) { IRSource val = value[i]; if (val != null && !val.flare && (Object)(object)val.transform != (Object)null) { return val; } } return null; } public static void Evaluate(Threat t, Aircraft aircraft, float spent) { t.ShotsNeeded = -1; t.Threshold = 0f; t.PerShotGain = 0f; if ((Object)(object)t.Missile == (Object)null) { return; } try { if (t.IsIR) { EvaluateIR(t, aircraft, spent); } else if (t.IsRadar) { EvaluateRadar(t, spent); } } catch { t.ShotsNeeded = -1; } } private static void EvaluateIR(Threat t, Aircraft aircraft, float spent) { //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_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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_008e: 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_0095: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) IRSeeker componentInChildren = ((Component)t.Missile).GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { return; } IRSource val = OwnIRSource((Unit)(object)aircraft); if (val == null || (Object)(object)val.transform == (Object)null || !TryGet(componentInChildren, "flareRejection", out var value) || value <= 0f || !TryGet(componentInChildren, "rangeFactor", out var value2) || value2 == null) { return; } float num = MaxRange(t.Missile); if (num <= 0f) { return; } Vector3 position = ((Component)componentInChildren).transform.position; Vector3 position2 = val.transform.position; Vector3 val2 = position2 - position; Vector3 normalized = ((Vector3)(ref val2)).normalized; float num2 = Vector3.Distance(position, position2); float num3 = Mathf.Clamp01(Vector3.Dot(-val.transform.forward, normalized)); float num4 = Mathf.Clamp01(Vector3.Dot(val.transform.forward, normalized)); float num5 = num3 * 0.5f + num4 * 2f; float num6 = value2.Evaluate(num2 / num); float num7 = Mathf.Clamp01(BackgroundBrightness(position, normalized)) * 2f; float num8 = num6 + num7; if (!(num8 <= 0.0001f)) { t.Threshold = val.intensity * (1f + num5) / num8; t.PerShotGain = 1f / value; float num9 = spent; if (((Unit)t.Missile).LocalSim && TryGet(componentInChildren, "dazzleAmount", out var value3)) { num9 = value3; } t.ShotsNeeded = ShotsFor(t.Threshold - num9, t.PerShotGain); } } private static void EvaluateRadar(Threat t, float spent) { MissileSeeker componentInChildren = ((Component)t.Missile).GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null || !TryGet(componentInChildren, "jamTolerance", out var value) || value < 0f) { return; } float value2 = 0f; if (TryGet(componentInChildren, "radarParameters", out var value3) && value3 != null) { TryGet(value3, "maxRange", out value2); } if (value2 <= 0f && TryGet(componentInChildren, "radarParams", out var value4) && value4 != null) { TryGet(value4, "maxRange", out value2); } if (value2 <= 0f) { value2 = MaxRange(t.Missile); } if (!(value2 <= 0f)) { float num = Mathf.Clamp01(1f - t.Range / value2); t.Threshold = value; t.PerShotGain = num * 0.5f / (1f + value); float num2 = spent; if (((Unit)t.Missile).LocalSim && TryGet(componentInChildren, "jamAccumulation", out var value5)) { num2 = value5; } t.ShotsNeeded = ShotsFor(t.Threshold - num2, t.PerShotGain); } } private static int ShotsFor(float remaining, float gain) { if (gain <= 0.0001f) { return -1; } if (remaining <= 0f) { return 0; } return Mathf.Clamp(Mathf.CeilToInt(remaining / gain), 0, 60); } private static float MaxRange(Missile missile) { try { WeaponInfo weaponInfo = missile.GetWeaponInfo(); if ((Object)(object)weaponInfo == (Object)null) { return 0f; } if (TryGet(weaponInfo, "targetRequirements", out var value) && value != null && TryGet(value, "maxRange", out var value2)) { return value2; } } catch { } return 0f; } private static float BackgroundBrightness(Vector3 seekerPos, Vector3 targetVector) { //IL_0026: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) try { LevelInfo i = NetworkSceneSingleton.i; if ((Object)(object)i == (Object)null || (Object)(object)i.sun == (Object)null) { return 0f; } float cloudOcclusion = i.GetCloudOcclusion(seekerPos); return Mathf.Clamp01(Vector3.Dot(targetVector, -((Component)i.sun).transform.forward)) * (1f - cloudOcclusion) * i.sun.color.b; } catch { return 0f; } } public static void EjectorCadence(Countermeasure cm, out float interval, out int grouping) { interval = 0.25f; grouping = 2; if (!((Object)(object)cm == (Object)null)) { if (TryGet(cm, "ejectionInterval", out var value) && value > 0f) { interval = value; } if (TryGet(cm, "ejectionGrouping", out var value2) && value2 > 0) { grouping = value2; } } } } internal sealed class Softkill { private bool _held; private byte _heldIndex; private Threat _holding; private int _ammoAtBurstStart; private float _lastTriggerAssert; public string Status { get; private set; } = "idle"; public void Reset(Aircraft aircraft) { if (_held && (Object)(object)aircraft != (Object)null) { try { aircraft.Countermeasures(false, _heldIndex); } catch { } } _held = false; _holding = null; Status = "idle"; } public void Tick(Aircraft aircraft, ThreatTable threats) { float timeSinceLevelLoad = Time.timeSinceLevelLoad; if (_held) { if (_holding != null && !(timeSinceLevelLoad >= _holding.BurstUntil) && !((Unit)aircraft).disabled) { DriveBurst(aircraft, timeSinceLevelLoad); return; } try { aircraft.Countermeasures(false, _heldIndex); } catch { } _held = false; if (_holding != null) { int num = CurrentAmmo(aircraft); int num2 = Mathf.Max(0, _ammoAtBurstStart - num); _holding.DecoysSpent += num2; _holding.SelfEstimate += (float)num2 * _holding.PerShotGain; _holding.NextBurstAt = timeSinceLevelLoad + Plugin.BurstGap.Value; Plugin.LogEngagement($"burst-end {_holding.Name} {_holding.SeekerType} salvo={_holding.Salvos} " + $"spent={num2} total={_holding.DecoysSpent} ammoLeft={num} tti={_holding.Tti:F1}"); _holding = null; } } else if (!Plugin.SoftkillEnabled.Value) { Status = "off"; } else { Threat threat = SelectTarget(aircraft, threats, timeSinceLevelLoad); if (threat == null) { Status = ((threats.Active.Count > 0) ? "holding" : "idle"); } else { Engage(aircraft, threat, timeSinceLevelLoad); } } } private void DriveBurst(Aircraft aircraft, float now) { CountermeasureManager countermeasureManager = aircraft.countermeasureManager; if (countermeasureManager == null) { return; } if (!aircraft.countermeasureTrigger && now - _lastTriggerAssert >= Plugin.TriggerRefresh.Value) { _lastTriggerAssert = now; try { aircraft.Countermeasures(true, _heldIndex); } catch { } } try { countermeasureManager.DeployCountermeasure(aircraft); } catch { } } private Threat SelectTarget(Aircraft aircraft, ThreatTable threats, float now) { Threat threat = null; for (int i = 0; i < threats.Active.Count; i++) { Threat threat2 = threats.Active[i]; if (threat2.Counterable && !(now < threat2.NextBurstAt) && threat2.Salvos < Plugin.MaxBursts.Value && (!threat2.IsRadar || !Plugin.RequireActiveLock.Value || threat2.ActiveLock)) { threat2.Lead = LeadTime(threat2); if (!(threat2.Tti > threat2.Lead) && (threat == null || threat2.Tti < threat.Tti)) { threat = threat2; } } } return threat; } private float LeadTime(Threat t) { float num = (t.IsIR ? Plugin.IrLead.Value : Plugin.RadarLead.Value); if (t.ShotsNeeded < 1) { return num; } int num2 = Mathf.Max(1, t.ShotsNeeded); float num3 = ((t.CadenceInterval > 0f) ? t.CadenceInterval : 0.25f); int num4 = Mathf.Max(1, t.CadenceGrouping); int num5 = Mathf.CeilToInt((float)num2 / (float)num4); int num6 = Mathf.CeilToInt((float)num5 / (float)Mathf.Max(1, Plugin.MaxPulsesPerBurst.Value)); return Mathf.Clamp((float)num5 * num3 + (float)Mathf.Max(0, num6 - 1) * Plugin.BurstGap.Value + Plugin.LeadMargin.Value, Plugin.MinLead.Value, num); } private void Engage(Aircraft aircraft, Threat t, float now) { CountermeasureManager countermeasureManager = aircraft.countermeasureManager; if (countermeasureManager == null) { Status = "no cm manager"; return; } string text; try { text = countermeasureManager.ChooseCountermeasure(t.Missile); } catch { text = null; } if (string.IsNullOrEmpty(text)) { t.NextBurstAt = now + 5f; Status = "no station for " + t.SeekerType; return; } byte activeIndex = countermeasureManager.activeIndex; Countermeasure activeCountermeasure = countermeasureManager.GetActiveCountermeasure(); int num = (((Object)(object)activeCountermeasure != (Object)null) ? activeCountermeasure.ammo : 0); int num2 = ((!(t.Tti <= Plugin.ReserveOverrideTti.Value)) ? Plugin.AmmoReserve.Value : 0); if (num <= num2) { t.NextBurstAt = now + 1f; Status = $"ammo {num} <= reserve {num2}"; return; } SeekerModel.EjectorCadence(activeCountermeasure, out var interval, out var grouping); t.CadenceInterval = interval; t.CadenceGrouping = grouping; int num3 = Mathf.Clamp(Mathf.CeilToInt((float)Mathf.Min((t.ShotsNeeded > 0) ? t.ShotsNeeded : Plugin.DefaultShots.Value, Mathf.Max(1, num - num2)) / (float)grouping), 1, Plugin.MaxPulsesPerBurst.Value); float num4 = (float)num3 * interval + 0.05f; try { aircraft.Countermeasures(true, activeIndex); } catch { Status = "deploy failed"; return; } _held = true; _heldIndex = activeIndex; _holding = t; _ammoAtBurstStart = num; _lastTriggerAssert = now; try { countermeasureManager.DeployCountermeasure(aircraft); } catch { } t.BurstUntil = now + num4; t.Salvos++; Status = $"{text} x{num3 * grouping} on {t.Name}"; Plugin.LogEngagement($"burst-start {t.Name} {t.SeekerType} tti={t.Tti:F2} range={t.Range:F0} lead={t.Lead:F2} " + $"need={t.ShotsNeeded} thr={t.Threshold:F2} gain={t.PerShotGain:F3} " + $"pulses={num3} hold={num4:F2} ammo={num} activeLock={t.ActiveLock}"); Plugin.Notify($"CM: {text} — {t.Name} {t.Tti:F1}s"); } private static int CurrentAmmo(Aircraft aircraft) { try { CountermeasureManager countermeasureManager = aircraft.countermeasureManager; Countermeasure val = ((countermeasureManager != null) ? countermeasureManager.GetActiveCountermeasure() : null); return ((Object)(object)val != (Object)null) ? val.ammo : 0; } catch { return 0; } } } internal sealed class Threat { public int Id; public Missile Missile; public string SeekerType = ""; public string Name = "?"; public float FirstSeen; public float LastSeen; public float Range; public float Closure; public float Tti; public bool ActiveLock; public bool EverActiveLock; public int Salvos; public int DecoysSpent; public float BurstUntil; public float NextBurstAt; public float CadenceInterval; public int CadenceGrouping = 2; public float SelfEstimate; public int InterceptorsFired; public int ShotsNeeded = -1; public float Threshold; public float PerShotGain; public float Lead; public bool Burst => Time.timeSinceLevelLoad < BurstUntil; public bool IsIR => SeekerType == "IR"; public bool IsRadar { get { if (!(SeekerType == "ARH")) { return SeekerType == "SARH"; } return true; } } public bool Counterable { get { if (!IsIR) { return IsRadar; } return true; } } } internal sealed class ThreatTable { private readonly Dictionary _threats = new Dictionary(); private readonly List _seen = new List(); private readonly List _gone = new List(); public readonly List Active = new List(); public List Resolved => _gone; public void Clear() { _threats.Clear(); Active.Clear(); _gone.Clear(); } public void Refresh(Aircraft aircraft) { //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Invalid comparison between Unknown and I4 _gone.Clear(); Active.Clear(); _seen.Clear(); MissileWarning val = null; try { val = aircraft.GetMissileWarningSystem(); } catch { } List list = (((Object)(object)val != (Object)null) ? val.knownMissiles : null); float timeSinceLevelLoad = Time.timeSinceLevelLoad; Vector3 position = ((Component)aircraft).transform.position; Vector3 val2 = (((Object)(object)((Unit)aircraft).rb != (Object)null) ? ((Unit)aircraft).rb.velocity : Vector3.zero); if (list != null) { for (int i = 0; i < list.Count; i++) { Missile val3 = list[i]; if ((Object)(object)val3 == (Object)null || ((Unit)val3).disabled) { continue; } int instanceID = ((Object)val3).GetInstanceID(); _seen.Add(instanceID); if (!_threats.TryGetValue(instanceID, out var value)) { value = new Threat { Id = instanceID, Missile = val3, FirstSeen = timeSinceLevelLoad }; try { value.SeekerType = val3.GetSeekerType() ?? ""; } catch { value.SeekerType = ""; } try { value.Name = val3.GetWeaponInfo()?.shortName ?? ((Object)val3).name; } catch { value.Name = "?"; } _threats[instanceID] = value; } value.Missile = val3; value.LastSeen = timeSinceLevelLoad; Vector3 val4 = ((Component)val3).transform.position - position; Vector3 val5 = (((Object)(object)((Unit)val3).rb != (Object)null) ? ((Unit)val3).rb.velocity : Vector3.zero) - val2; value.Range = ((Vector3)(ref val4)).magnitude; value.Closure = ((value.Range > 0.01f) ? (0f - Vector3.Dot(val5, val4 / value.Range)) : 0f); value.Tti = ((value.Closure > 1f) ? (value.Range / value.Closure) : float.PositiveInfinity); value.ActiveLock = (int)val3.seekerMode == 1; if (value.ActiveLock) { value.EverActiveLock = true; } Active.Add(value); } } foreach (KeyValuePair threat in _threats) { if (!_seen.Contains(threat.Key)) { _gone.Add(threat.Value); } } for (int j = 0; j < _gone.Count; j++) { _threats.Remove(_gone[j].Id); } } public Threat MostUrgent(bool counterableOnly) { Threat threat = null; for (int i = 0; i < Active.Count; i++) { Threat threat2 = Active[i]; if ((!counterableOnly || threat2.Counterable) && (threat == null || threat2.Tti < threat.Tti)) { threat = threat2; } } return threat; } } }