using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using AIGraph;
using Agents;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using Enemies;
using GTFO.CrashTestDummies.Core;
using GameData;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using LevelGeneration;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("GTFO.CrashTestDummies")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+c94036a7ac38a053ae1799b501a5a499432ec1ec")]
[assembly: AssemblyProduct("GTFO.CrashTestDummies")]
[assembly: AssemblyTitle("GTFO.CrashTestDummies")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[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 GTFO.CrashTestDummies
{
internal static class AmbientSpawner
{
public static IEnumerator Routine()
{
PlayerAgent val = default(PlayerAgent);
while (true)
{
yield return (object)new WaitForSeconds(Settings.AmbientIntervalSeconds.Value);
if (Settings.ModEnabled.Value && Settings.AmbientSpawnEnabled.Value && SNet.IsMaster && PlayerManager.TryGetLocalPlayerAgent(ref val) && !((Object)(object)val == (Object)null))
{
int count = Random.Range(Settings.AmbientCountMin.Value, Settings.AmbientCountMax.Value + 1);
yield return EnemySpawner.SpawnAround(((Component)val).transform.position, Settings.AmbientSkipFirstMeters.Value, Settings.AmbientMaxDistanceMeters.Value, Settings.MinSpacingMeters.Value, count, (Vector3 point) => Visibility.IsHiddenFromAllPlayers(point, Settings.OutOfSightHalfAngleDegrees.Value));
}
}
}
}
internal static class Settings
{
public static ConfigEntry<bool> ModEnabled;
public static ConfigEntry<int> MaxTotalEnemies;
public static ConfigEntry<float> OutOfSightHalfAngleDegrees;
public static ConfigEntry<int> SpawnCount;
public static ConfigEntry<float> SkipFirstMeters;
public static ConfigEntry<float> MaxDistanceMeters;
public static ConfigEntry<float> MinSpacingMeters;
public static ConfigEntry<string> EnemyNameFilter;
public static ConfigEntry<float> SpawnDelaySeconds;
public static ConfigEntry<bool> SpawnAsSleepers;
public static ConfigEntry<bool> AmbushOnAlarmDoors;
public static ConfigEntry<int> AmbushCount;
public static ConfigEntry<float> AmbushSkipFirstMeters;
public static ConfigEntry<float> AmbushMaxDistanceMeters;
public static ConfigEntry<bool> AmbientSpawnEnabled;
public static ConfigEntry<float> AmbientIntervalSeconds;
public static ConfigEntry<int> AmbientCountMin;
public static ConfigEntry<int> AmbientCountMax;
public static ConfigEntry<float> AmbientSkipFirstMeters;
public static ConfigEntry<float> AmbientMaxDistanceMeters;
public static ConfigEntry<bool> CleanupEnabled;
public static ConfigEntry<float> CleanupCheckIntervalSeconds;
public static ConfigEntry<float> CleanupAfterSeconds;
public static void Bind(ConfigFile config)
{
ModEnabled = config.Bind<bool>("General", "Enabled", true, "Master switch. Disable to stop dummies from spawning without uninstalling the mod.");
MaxTotalEnemies = config.Bind<int>("General", "MaxTotalEnemies", 300, "Population cap: how many of this mod's dummies may be alive at once, combined across the drop-point scatter, alarm-door ambushes and the ambient scatter. It's a ceiling on the current count, not a lifetime total - as dummies die or get recycled by the Cleanup settings, room opens up for more to spawn.");
OutOfSightHalfAngleDegrees = config.Bind<float>("General", "OutOfSightHalfAngleDegrees", 70f, "Used by every spawn trigger and by cleanup: a spot is treated as hidden from a player if it falls outside this many degrees of the centre of their view, or something blocks the line of sight to it. The bigger this is, the more conservative (and more likely to find nowhere to spawn in a small room).");
SpawnCount = config.Bind<int>("Spawning", "SpawnCount", 100, "How many enemies to scatter between SkipFirstMeters and MaxDistanceMeters, at the level's own enemy spawn spots and random walkable points in each room.");
SkipFirstMeters = config.Bind<float>("Spawning", "SkipFirstMeters", 10f, "No dummy is placed closer than this to where you step off the elevator.");
MaxDistanceMeters = config.Bind<float>("Spawning", "MaxDistanceMeters", 100f, "No dummy is placed farther than this from where you step off the elevator.");
MinSpacingMeters = config.Bind<float>("Spawning", "MinSpacingMeters", 3f, "Minimum distance between any two enemies, just enough to keep them from overlapping.");
EnemyNameFilter = config.Bind<string>("Spawning", "EnemyNameFilter", "Striker_Hibernate", "Case-insensitive substring match against the game's enemy data block names; of the matches, the one with the lowest max health is spawned. Default is the plain sleeping Striker. If nothing matches, the mod falls back to whichever enemy has the lowest max health.");
SpawnDelaySeconds = config.Bind<float>("Spawning", "SpawnDelaySeconds", 2f, "Delay after you spawn into the level before dummies are created, to let the level finish settling.");
SpawnAsSleepers = config.Bind<bool>("Spawning", "SpawnAsSleepers", true, "true: dummies spawn asleep (hibernating) and can be stealth-killed. false: dummies spawn awake and aggressive.");
AmbushOnAlarmDoors = config.Bind<bool>("Ambush", "AmbushOnAlarmDoors", true, "When a security door locked behind an alarm scan (chained puzzle) finishes opening, scatter extra dummies on the far side of it, same as the level-start scatter but centered on that doorway.");
AmbushCount = config.Bind<int>("Ambush", "AmbushCount", 20, "How many enemies to scatter on the far side of an alarm door when it opens.");
AmbushSkipFirstMeters = config.Bind<float>("Ambush", "AmbushSkipFirstMeters", 10f, "No ambush dummy is placed closer than this to the far side of the doorway.");
AmbushMaxDistanceMeters = config.Bind<float>("Ambush", "AmbushMaxDistanceMeters", 100f, "No ambush dummy is placed farther than this from the far side of the doorway.");
AmbientSpawnEnabled = config.Bind<bool>("Ambient", "AmbientSpawnEnabled", true, "Every AmbientIntervalSeconds, scatter a few more dummies near the local player, skipping any spot currently in a player's view so they never visibly pop in.");
AmbientIntervalSeconds = config.Bind<float>("Ambient", "AmbientIntervalSeconds", 60f, "How often, in seconds, to run the ambient scatter.");
AmbientCountMin = config.Bind<int>("Ambient", "AmbientCountMin", 50, "Minimum enemies to scatter each time the ambient scatter runs (inclusive).");
AmbientCountMax = config.Bind<int>("Ambient", "AmbientCountMax", 100, "Maximum enemies to scatter each time the ambient scatter runs (inclusive). A random count between AmbientCountMin and this is picked on every run.");
AmbientSkipFirstMeters = config.Bind<float>("Ambient", "AmbientSkipFirstMeters", 10f, "No ambient dummy is placed closer than this to the local player.");
AmbientMaxDistanceMeters = config.Bind<float>("Ambient", "AmbientMaxDistanceMeters", 100f, "No ambient dummy is placed farther than this from the local player.");
CleanupEnabled = config.Bind<bool>("Cleanup", "CleanupEnabled", true, "Recycle dummies that have sat alive, untouched and out of sight for a while, so the population cap keeps refilling with fresh dummies instead of staying maxed out with old ones.");
CleanupCheckIntervalSeconds = config.Bind<float>("Cleanup", "CleanupCheckIntervalSeconds", 30f, "How often, in seconds, to check for dummies to recycle.");
CleanupAfterSeconds = config.Bind<float>("Cleanup", "CleanupAfterSeconds", 300f, "A dummy is only eligible to be recycled once it has been alive this long (seconds) and is currently out of sight - it's never removed while a player might be looking at it.");
}
}
internal class CoroutineHost : MonoBehaviour
{
public CoroutineHost(IntPtr ptr)
: base(ptr)
{
}
}
internal static class DoorAmbush
{
private static readonly Dictionary<int, eDoorStatus> PreviousStatus = new Dictionary<int, eDoorStatus>();
public static void OnSecurityDoorStatusChanged(LG_SecurityDoor? door, eDoorStatus status)
{
//IL_0038: 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_0040: Invalid comparison between Unknown and I4
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Invalid comparison between Unknown and I4
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Expected O, but got Unknown
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)door == (Object)null || !Settings.AmbushOnAlarmDoors.Value || !SNet.IsMaster)
{
return;
}
int instanceID = ((Object)door).GetInstanceID();
PreviousStatus.TryGetValue(instanceID, out var value);
PreviousStatus[instanceID] = status;
if ((int)value != 4 || (int)status != 10)
{
return;
}
AIG_CourseNode val = FindFarSideNode(door);
if (val == null)
{
Plugin.Logger.LogWarning((object)"CrashTestDummies: alarm door opened but no far-side node was found - nothing spawned.");
return;
}
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(67, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("CrashTestDummies: alarm door opened - ambushing the far side near ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Vector3>(val.Position);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(".");
}
logger.LogInfo(val2);
((MonoBehaviour)Plugin.CoroutineHostInstance).StartCoroutine(CollectionExtensions.WrapToIl2Cpp(EnemySpawner.SpawnAround(val.Position, Settings.AmbushSkipFirstMeters.Value, Settings.AmbushMaxDistanceMeters.Value, Settings.MinSpacingMeters.Value, Settings.AmbushCount.Value, (Vector3 point) => Visibility.IsHiddenFromAllPlayers(point, Settings.OutOfSightHalfAngleDegrees.Value))));
}
private static AIG_CourseNode? FindFarSideNode(LG_SecurityDoor door)
{
//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_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
LG_Gate gate = door.Gate;
List<AIG_INode> val = ((gate != null) ? gate.Nodes : null);
if (val == null || val.Count == 0)
{
return null;
}
Vector3 position = ((Component)door).transform.position;
PlayerAgent val2 = default(PlayerAgent);
if (PlayerManager.TryGetCloseEnoughPlayerAgent(position, 15f, ref val2) && (Object)(object)val2 != (Object)null)
{
position = ((Component)val2).transform.position;
}
AIG_CourseNode result = null;
float num = -1f;
for (int i = 0; i < val.Count; i++)
{
AIG_INode obj = val[i];
AIG_CourseNode val3 = ((obj != null) ? ((Il2CppObjectBase)obj).TryCast<AIG_CourseNode>() : null);
if (val3 != null)
{
float num2 = Vector3.Distance(val3.Position, position);
if (num2 > num)
{
num = num2;
result = val3;
}
}
}
return result;
}
}
internal static class EnemyCleanup
{
private readonly record struct TrackedEnemy(EnemyAgent Agent, float SpawnTime);
private static readonly List<TrackedEnemy> Tracked = new List<TrackedEnemy>();
public static int AliveCount
{
get
{
Tracked.RemoveAll((TrackedEnemy t) => (Object)(object)t.Agent == (Object)null || !((Agent)t.Agent).Alive);
return Tracked.Count;
}
}
public static void Track(EnemyAgent? agent)
{
if ((Object)(object)agent != (Object)null)
{
Tracked.Add(new TrackedEnemy(agent, Time.time));
}
}
public static void ResetForNewExpedition()
{
Tracked.Clear();
}
public static IEnumerator Routine()
{
bool flag = default(bool);
while (true)
{
yield return (object)new WaitForSeconds(Settings.CleanupCheckIntervalSeconds.Value);
if (!Settings.ModEnabled.Value || !Settings.CleanupEnabled.Value || !SNet.IsMaster)
{
continue;
}
float time = Time.time;
int num = 0;
for (int num2 = Tracked.Count - 1; num2 >= 0; num2--)
{
TrackedEnemy trackedEnemy = Tracked[num2];
if ((Object)(object)trackedEnemy.Agent == (Object)null || !((Agent)trackedEnemy.Agent).Alive)
{
Tracked.RemoveAt(num2);
}
else if (!(time - trackedEnemy.SpawnTime < Settings.CleanupAfterSeconds.Value) && Visibility.IsHiddenFromAllPlayers(((Component)trackedEnemy.Agent).transform.position, Settings.OutOfSightHalfAngleDegrees.Value))
{
SNet_Replicator replicator = ((Agent)trackedEnemy.Agent).m_replicator;
if (replicator != null)
{
replicator.Despawn();
}
Tracked.RemoveAt(num2);
num++;
}
}
if (num > 0)
{
ManualLogSource logger = Plugin.Logger;
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(61, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CrashTestDummies: recycled ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" forgotten dummies (");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(AliveCount);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" still alive).");
}
logger.LogInfo(val);
}
}
}
}
internal static class EnemySpawner
{
private readonly record struct SpawnSpot(AIG_CourseNode Node, Vector3 Position, Quaternion Rotation);
private const int RandomPointsPerRoom = 15;
private const float RoomReachMargin = 40f;
private static EnemyDataBlock? _cachedTarget;
private static string? _cachedFilter;
public static IEnumerator SpawnRoutine(PlayerAgent localAgent)
{
yield return (object)new WaitForSeconds(Settings.SpawnDelaySeconds.Value);
if (!SNet.IsMaster)
{
yield break;
}
if ((Object)(object)localAgent == (Object)null || ((Agent)localAgent).CourseNode == null)
{
Plugin.Logger.LogWarning((object)"CrashTestDummies: the local player has no course node yet - nothing spawned.");
yield break;
}
yield return SpawnAround(((Component)localAgent).transform.position, Settings.SkipFirstMeters.Value, Settings.MaxDistanceMeters.Value, Settings.MinSpacingMeters.Value, Settings.SpawnCount.Value, (Vector3 point) => Visibility.IsHiddenFromAllPlayers(point, Settings.OutOfSightHalfAngleDegrees.Value));
}
public static IEnumerator SpawnAround(Vector3 origin, float skipMeters, float maxMeters, float minSpacing, int count, Func<Vector3, bool>? isAllowed = null)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
EnemyDataBlock target = GetTargetEnemy();
if (target == null)
{
Plugin.Logger.LogError((object)"CrashTestDummies: no eligible enemy data block found - nothing spawned.");
yield break;
}
int num = Settings.MaxTotalEnemies.Value - EnemyCleanup.AliveCount;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val;
if (num <= 0)
{
ManualLogSource logger = Plugin.Logger;
val = new BepInExInfoLogInterpolatedStringHandler(69, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CrashTestDummies: MaxTotalEnemies (");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(Settings.MaxTotalEnemies.Value);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(") already alive - nothing spawned.");
}
logger.LogInfo(val);
yield break;
}
int num2 = Math.Min(count, num);
AgentMode mode = (AgentMode)((!Settings.SpawnAsSleepers.Value) ? 1 : 4);
List<SpawnSpot> spots = GatherSpawnSpots(origin, maxMeters);
if (isAllowed != null)
{
spots = spots.Where((SpawnSpot s) => isAllowed(s.Position)).ToList();
}
Shuffle(spots);
List<int> picks = SpawnPicker.Pick((IReadOnlyList<Vector3>)spots.Select((SpawnSpot s) => ToNumerics(s.Position)).ToList(), ToNumerics(origin), skipMeters, maxMeters, minSpacing, num2);
if (picks.Count < num2)
{
ManualLogSource logger2 = Plugin.Logger;
BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(69, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("CrashTestDummies: only ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(picks.Count);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" of ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num2);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" spots fit the distance and spacing rules.");
}
logger2.LogWarning(val2);
}
ManualLogSource logger3 = Plugin.Logger;
val = new BepInExInfoLogInterpolatedStringHandler(35, 3, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CrashTestDummies: spawning ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(picks.Count);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("x '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((GameDataBlockBase<EnemyDataBlock>)(object)target).name);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' (");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<AgentMode>(mode);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(").");
}
logger3.LogInfo(val);
for (int i = 0; i < picks.Count; i++)
{
SpawnSpot spawnSpot = spots[picks[i]];
EnemyCleanup.Track(EnemyAllocator.Current.SpawnEnemy(((GameDataBlockBase<EnemyDataBlock>)(object)target).persistentID, spawnSpot.Node, mode, spawnSpot.Position, spawnSpot.Rotation, (EnemyGroup)null, -1));
if (i % 5 == 4)
{
yield return null;
}
}
ManualLogSource logger4 = Plugin.Logger;
val = new BepInExInfoLogInterpolatedStringHandler(64, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CrashTestDummies: done spawning (");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(EnemyCleanup.AliveCount);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(Settings.MaxTotalEnemies.Value);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" of the population cap alive).");
}
logger4.LogInfo(val);
}
private static List<SpawnSpot> GatherSpawnSpots(Vector3 origin, float maxMeters)
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: 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)
List<SpawnSpot> list = new List<SpawnSpot>();
List<AIG_CourseNode> s_allNodes = AIG_CourseNode.s_allNodes;
if (s_allNodes == null)
{
return list;
}
float num = maxMeters + 40f;
for (int i = 0; i < s_allNodes.Count; i++)
{
AIG_CourseNode val = s_allNodes[i];
if (val == null || Vector3.Distance(val.Position, origin) > num)
{
continue;
}
LG_Area area = val.m_area;
List<Transform> val2 = ((area != null) ? area.m_enemySpawnPoints : null);
if (val2 != null)
{
for (int j = 0; j < val2.Count; j++)
{
Transform val3 = val2[j];
if ((Object)(object)val3 != (Object)null)
{
list.Add(new SpawnSpot(val, val3.position, val3.rotation));
}
}
}
for (int k = 0; k < 15; k++)
{
list.Add(new SpawnSpot(val, val.GetRandomPositionInside(), Quaternion.Euler(0f, Random.Range(0f, 360f), 0f)));
}
}
return list;
}
private static void Shuffle<T>(List<T> list)
{
for (int num = list.Count - 1; num > 0; num--)
{
int num2 = Random.Range(0, num + 1);
int index = num;
int index2 = num2;
T value = list[num2];
T value2 = list[num];
list[index] = value;
list[index2] = value2;
}
}
private static Vector3 ToNumerics(Vector3 v)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
return new Vector3(v.x, v.y, v.z);
}
private static EnemyDataBlock? GetTargetEnemy()
{
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_00f8: Expected O, but got Unknown
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_00c1: Expected O, but got Unknown
//IL_013e: Unknown result type (might be due to invalid IL or missing references)
//IL_0145: Expected O, but got Unknown
string value = Settings.EnemyNameFilter.Value;
if (_cachedTarget != null && _cachedFilter == value)
{
return _cachedTarget;
}
Il2CppArrayBase<EnemyDataBlock> allBlocks = GameDataBlockBase<EnemyDataBlock>.GetAllBlocks();
if (allBlocks == null || allBlocks.Length == 0)
{
return null;
}
Dictionary<uint, EnemyDataBlock> dictionary = new Dictionary<uint, EnemyDataBlock>();
List<EnemyCandidate> list = new List<EnemyCandidate>();
foreach (EnemyDataBlock item in allBlocks)
{
if (item != null)
{
EnemyBalancingDataBlock block = GameDataBlockBase<EnemyBalancingDataBlock>.GetBlock(item.BalancingDataId);
dictionary[((GameDataBlockBase<EnemyDataBlock>)(object)item).persistentID] = item;
list.Add(new EnemyCandidate(((GameDataBlockBase<EnemyDataBlock>)(object)item).persistentID, ((GameDataBlockBase<EnemyDataBlock>)(object)item).name, ((GameDataBlockBase<EnemyDataBlock>)(object)item).internalEnabled, (Object)(object)EnemyPrefabManager.GetEnemyPrefab(((GameDataBlockBase<EnemyDataBlock>)(object)item).persistentID) != (Object)null, (block != null) ? block.Health.HealthMax : 0f));
}
}
DummySelection obj = DummySelector.Select((IEnumerable<EnemyCandidate>)list, value);
bool flag = default(bool);
if (obj.FilterUnmatched)
{
ManualLogSource logger = Plugin.Logger;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(94, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CrashTestDummies: no enemy matched filter '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(value);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("', falling back to the weakest enemy by max health.");
}
logger.LogWarning(val);
}
EnemyCandidate chosen = obj.Chosen;
if (chosen != (EnemyCandidate)null)
{
ManualLogSource logger2 = Plugin.Logger;
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(54, 3, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("CrashTestDummies: dummy type is '");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(chosen.Name);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' (id ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<uint>(chosen.Id);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", max health ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>(chosen.HealthMax);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(").");
}
logger2.LogInfo(val2);
}
_cachedTarget = ((chosen != (EnemyCandidate)null) ? dictionary[chosen.Id] : null);
_cachedFilter = value;
return _cachedTarget;
}
}
[HarmonyPatch]
internal static class LevelPatches
{
private static readonly SpawnGate Gate = new SpawnGate();
private static Coroutine? _ambientRoutine;
private static Coroutine? _cleanupRoutine;
[HarmonyPatch(typeof(Builder), "Build")]
[HarmonyPrefix]
private static void Pre_Build()
{
Gate.Reset();
EnemyCleanup.ResetForNewExpedition();
if (_ambientRoutine != null)
{
((MonoBehaviour)Plugin.CoroutineHostInstance).StopCoroutine(_ambientRoutine);
_ambientRoutine = null;
}
if (_cleanupRoutine != null)
{
((MonoBehaviour)Plugin.CoroutineHostInstance).StopCoroutine(_cleanupRoutine);
_cleanupRoutine = null;
}
}
[HarmonyPatch(typeof(GS_InLevel), "Enter")]
[HarmonyPostfix]
private static void Post_EnterLevel()
{
if (Gate.TryBegin(Settings.ModEnabled.Value, SNet.IsMaster))
{
PlayerAgent val = default(PlayerAgent);
if (!PlayerManager.TryGetLocalPlayerAgent(ref val) || (Object)(object)val == (Object)null)
{
Plugin.Logger.LogWarning((object)"CrashTestDummies: entered the level without a local player agent - nothing spawned.");
}
else
{
((MonoBehaviour)Plugin.CoroutineHostInstance).StartCoroutine(CollectionExtensions.WrapToIl2Cpp(StartupSequence(val)));
}
}
}
private static IEnumerator StartupSequence(PlayerAgent localAgent)
{
yield return EnemySpawner.SpawnRoutine(localAgent);
_ambientRoutine = ((MonoBehaviour)Plugin.CoroutineHostInstance).StartCoroutine(CollectionExtensions.WrapToIl2Cpp(AmbientSpawner.Routine()));
_cleanupRoutine = ((MonoBehaviour)Plugin.CoroutineHostInstance).StartCoroutine(CollectionExtensions.WrapToIl2Cpp(EnemyCleanup.Routine()));
}
[HarmonyPatch(typeof(LG_SecurityDoor), "OnSyncDoorStatusChange")]
[HarmonyPostfix]
private static void Post_SecurityDoorStatusChange(LG_SecurityDoor __instance, pDoorState state, bool isRecall)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
if (!isRecall)
{
DoorAmbush.OnSecurityDoorStatusChanged(__instance, state.status);
}
}
}
[BepInPlugin("MindAttic.GTFO.CrashTestDummies", "GTFO.CrashTestDummies", "3.0.0")]
[BepInProcess("GTFO.exe")]
public class Plugin : BasePlugin
{
public const string GUID = "MindAttic.GTFO.CrashTestDummies";
public const string NAME = "GTFO.CrashTestDummies";
public const string VERSION = "3.0.0";
internal static ManualLogSource Logger { get; private set; }
internal static CoroutineHost CoroutineHostInstance { get; private set; }
public override void Load()
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Expected O, but got Unknown
Logger = ((BasePlugin)this).Log;
Settings.Bind(((BasePlugin)this).Config);
CoroutineHostInstance = ((BasePlugin)this).AddComponent<CoroutineHost>();
new Harmony("MindAttic.GTFO.CrashTestDummies").PatchAll();
ManualLogSource logger = Logger;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(70, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("GTFO.CrashTestDummies");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("3.0.0");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded - low-level enemy flood armed for the next expedition entry.");
}
logger.LogInfo(val);
}
}
internal static class Visibility
{
public static bool IsHiddenFromAllPlayers(Vector3 point, float viewHalfAngleDegrees)
{
//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_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: 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)
List<PlayerAgent> playerAgentsInLevel = PlayerManager.PlayerAgentsInLevel;
if (playerAgentsInLevel == null)
{
return true;
}
for (int i = 0; i < playerAgentsInLevel.Count; i++)
{
PlayerAgent val = playerAgentsInLevel[i];
if ((Object)(object)val == (Object)null || !((Agent)val).Alive)
{
continue;
}
FPSCamera fPSCamera = val.FPSCamera;
if (!((Object)(object)fPSCamera == (Object)null))
{
Vector3 eyePosition = ((Agent)val).EyePosition;
Vector3 val2 = point - eyePosition;
if (!(Vector3.Angle(((Component)fPSCamera).transform.forward, val2) > viewHalfAngleDegrees) && !Physics.Linecast(eyePosition, point))
{
return false;
}
}
}
return true;
}
}
}