Decompiled source of Sun Chase v1.0.0

SunChase.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
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.AddressableAssets;
using UnityEngine.Events;
using UnityEngine.ResourceManagement.AsyncOperations;
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("SunChase")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0c5c7ba8d3508d758a3dd573876bc63099f0c052")]
[assembly: AssemblyProduct("SunChase")]
[assembly: AssemblyTitle("SunChase")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SunChase
{
	[BepInPlugin("lakisa.sunchase", "Sun Chase", "1.0.0")]
	public class SunChasePlugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> SpeedMode;

		private static readonly string[] Modes = new string[3] { "vanilla", "mass_logic", "player" };

		private static readonly string[] PlayerModes = new string[3] { "walk", "run", "crouch" };

		private const string Usage = "sun_spd <vanilla|mass_logic|player [run|walk|crouch]|m/s>";

		private CommandConsole console;

		private static SunChasePlugin instance;

		private static readonly HashSet<DEN_DeathFloor> replaced = new HashSet<DEN_DeathFloor>();

		private void Awake()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Replace the mass with the Shuttered Rift's bomb.");
			Harmony val = new Harmony("lakisa.sunchase");
			val.Patch((MethodBase)AccessTools.Method(typeof(DEN_DeathFloor), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(SunChasePlugin), "MassStartPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			val.Patch((MethodBase)AccessTools.Method(typeof(DEN_DeathFloor), "Update", (Type[])null, (Type[])null), new HarmonyMethod(typeof(SunChasePlugin), "MassUpdatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			SpeedMode = ((BaseUnityPlugin)this).Config.Bind<string>("General", "SunSpeed", "mass_logic", "How fast the sun's edge grows: vanilla (the Rift's), mass_logic (speeds up and slows down exactly like the mass would), player run / player walk / player crouch (your own speed for that), or a number in m/s. Also the sun_spd console command.");
			instance = this;
			SceneManager.sceneLoaded += delegate(Scene s, LoadSceneMode _)
			{
				if (((Scene)(ref s)).name == "Main-Menu")
				{
					StartFind();
				}
			};
		}

		private void Update()
		{
			if ((Object)(object)CommandConsole.instance == (Object)null || (Object)(object)CommandConsole.instance == (Object)(object)console)
			{
				return;
			}
			console = CommandConsole.instance;
			CommandConsole.BuildCommand("sun_spd", (Action<string[]>)SunSpeedCommand).NotCheat().Description("sun_spd <vanilla|mass_logic|player [run|walk|crouch]|m/s>: how fast the sun grows. vanilla = the Rift's, mass_logic = speeds up and slows down like the mass, player = your own run / walk / crouch speed")
				.AutocompleteCustom((Action<CommandAutocomplete>)delegate(CommandAutocomplete ac)
				{
					if (ac.activeArg == 0)
					{
						ac.FromArray((IReadOnlyList<string>)Modes);
					}
					else if (ac.activeArg == 1)
					{
						ac.FromArray((IReadOnlyList<string>)PlayerModes);
					}
				});
		}

		internal static string Normalize(string[] args)
		{
			if (args == null || args.Length == 0)
			{
				return null;
			}
			string text = args[0].ToLowerInvariant();
			switch (text)
			{
			case "catch_up":
			case "catchup":
			case "mass":
				return "mass_logic";
			case "relative":
				return "player walk";
			case "player":
			{
				string text2 = ((args.Length > 1) ? args[1].ToLowerInvariant() : "walk");
				if (text2 == "sprint")
				{
					text2 = "run";
				}
				if (!PlayerModes.Contains(text2))
				{
					return null;
				}
				return "player " + text2;
			}
			default:
			{
				if (Modes.Contains(text))
				{
					return text;
				}
				if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !(result >= 0f))
				{
					return null;
				}
				return text;
			}
			}
		}

		private static void SunSpeedCommand(string[] args)
		{
			if (args.Length == 0)
			{
				CommandConsole.Log(string.Format("sun_spd is {0} ({1:0.0} m/s now). {2}", SpeedMode.Value, BombRunner.CurrentSpeed, "sun_spd <vanilla|mass_logic|player [run|walk|crouch]|m/s>"));
				return;
			}
			string text = Normalize(args);
			if (text == null)
			{
				CommandConsole.Log("Unknown sun_spd '" + string.Join(" ", args) + "'. sun_spd <vanilla|mass_logic|player [run|walk|crouch]|m/s>");
				return;
			}
			SpeedMode.Value = text;
			CommandConsole.Log("sun_spd set to " + text);
			Log.LogInfo((object)("> sun_spd " + text));
		}

		internal static void StartFind()
		{
			if (Bomb.State == Bomb.Status.NotTried && (Object)(object)instance != (Object)null)
			{
				((MonoBehaviour)instance).StartCoroutine(Bomb.Find());
			}
		}

		private static void MassStartPostfix(DEN_DeathFloor __instance)
		{
			//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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled.Value && ModeOk())
			{
				replaced.RemoveWhere((DEN_DeathFloor m) => (Object)(object)m == (Object)null);
				replaced.Add(__instance);
				Vector3 position = ((Component)__instance).transform.position;
				StartFind();
				BombRunner.Make(position, __instance);
				if ((Object)(object)__instance.visualRoot != (Object)null)
				{
					__instance.visualRoot.SetActive(false);
				}
				AudioSource[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<AudioSource>(true);
				foreach (AudioSource obj in componentsInChildren)
				{
					obj.Stop();
					((Behaviour)obj).enabled = false;
				}
				Log.LogInfo((object)($"Mass replaced by the bomb at {position}" + ((Bomb.State == Bomb.Status.Found) ? "" : " (waiting for the Rift's bomb to load)")));
			}
		}

		private static bool MassUpdatePrefix(DEN_DeathFloor __instance)
		{
			return !replaced.Contains(__instance);
		}

		internal static void GiveBack(DEN_DeathFloor mass)
		{
			if (!((Object)(object)mass == (Object)null))
			{
				replaced.Remove(mass);
				if ((Object)(object)mass.visualRoot != (Object)null)
				{
					mass.visualRoot.SetActive(true);
				}
				AudioSource[] componentsInChildren = ((Component)mass).GetComponentsInChildren<AudioSource>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					((Behaviour)componentsInChildren[i]).enabled = true;
				}
				Log.LogWarning((object)"No Shuttered Rift bomb: the mass stays.");
			}
		}

		private static bool ModeOk()
		{
			M_Gamemode baseGamemode = CL_GameManager.GetBaseGamemode();
			string text = (baseGamemode?.gamemodeName + ((baseGamemode != null) ? ((Object)baseGamemode).name : null)).ToLowerInvariant();
			if (!text.Contains("arena"))
			{
				return !text.Contains("gauntlet");
			}
			return false;
		}
	}
	internal static class Bomb
	{
		public enum Status
		{
			NotTried,
			Loading,
			Found,
			Missing
		}

		public struct Call
		{
			public Object target;

			public string method;
		}

		public class FieldPath
		{
			public FieldInfo field;

			public int index = -1;

			public FieldInfo inner;

			public UnityEventBase Get(object o)
			{
				object obj = field.GetValue(o);
				if (index >= 0)
				{
					obj = ((obj is IList list && index < list.Count && list[index] != null) ? inner.GetValue(list[index]) : null);
				}
				return (UnityEventBase)((obj is UnityEventBase) ? obj : null);
			}

			public override string ToString()
			{
				if (index >= 0)
				{
					return $"{field.Name}[{index}].{inner.Name}";
				}
				return field.Name;
			}
		}

		public static Status State;

		public static GameObject Template;

		public static int[] SunPath;

		public static int[] ButtonPath;

		public static Type ButtonType;

		public static FieldPath ButtonEvent;

		public static int[] SequencePath;

		private const string BombLevel = "CH_Shutter_Pier_End_Big";

		private const int MaxModuleSize = 400;

		private static GameObject holder;

		public static bool Done
		{
			get
			{
				if (State != Status.Found)
				{
					return State == Status.Missing;
				}
				return true;
			}
		}

		public static IEnumerator Find()
		{
			State = Status.Loading;
			List<LevelAssetHolder> levels = ((AccessTools.Field(typeof(CL_AssetManager), "databases").GetValue(null) is IEnumerable<WKDatabaseHolder> source) ? (from val3 in source.Where((WKDatabaseHolder d) => d?.database?.levelAssets != null).SelectMany((WKDatabaseHolder d) => d.database.levelAssets)
				where val3?.levelAssetReference != null && val3.id.IndexOf("shutter", StringComparison.OrdinalIgnoreCase) >= 0
				orderby (val3.id.IndexOf("CH_Shutter_Pier_End_Big", StringComparison.OrdinalIgnoreCase) < 0) ? 1 : 0
				select val3).ToList() : null) ?? new List<LevelAssetHolder>();
			foreach (LevelAssetHolder l in levels)
			{
				AsyncOperationHandle<GameObject> op;
				try
				{
					op = Addressables.LoadAssetAsync<GameObject>(((AssetReference)l.levelAssetReference).RuntimeKey);
				}
				catch (Exception ex)
				{
					SunChasePlugin.Log.LogWarning((object)("Load " + l.id + ": " + ex.Message));
					continue;
				}
				while (!op.IsDone)
				{
					yield return null;
				}
				GameObject val = (((int)op.Status == 1) ? op.Result : null);
				ENV_KillBubble val2 = (((Object)(object)val != (Object)null) ? val.GetComponentsInChildren<ENV_KillBubble>(true).FirstOrDefault() : null);
				if ((Object)(object)val2 == (Object)null)
				{
					Addressables.Release<GameObject>(op);
					continue;
				}
				bool flag = false;
				try
				{
					flag = Cut(val, val2);
				}
				catch (Exception arg)
				{
					SunChasePlugin.Log.LogError((object)$"Cutting the bomb out of {l.id}: {arg}");
				}
				if (!flag)
				{
					Addressables.Release<GameObject>(op);
					continue;
				}
				SunChasePlugin.Log.LogInfo((object)("Bomb module cut from " + l.id));
				State = Status.Found;
				yield break;
			}
			SunChasePlugin.Log.LogWarning((object)$"No bomb found in {levels.Count} Shuttered Rift levels.");
			State = Status.Missing;
		}

		private static bool Cut(GameObject level, ENV_KillBubble kb)
		{
			//IL_0473: Unknown result type (might be due to invalid IL or missing references)
			//IL_047d: Expected O, but got Unknown
			//IL_04b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c4: Unknown result type (might be due to invalid IL or missing references)
			Transform root = level.transform;
			List<MonoBehaviour> list = (from m in level.GetComponentsInChildren<MonoBehaviour>(true)
				where (Object)(object)m != (Object)null
				select m).ToList();
			UT_Timer seq = list.OfType<UT_Timer>().FirstOrDefault((Func<UT_Timer, bool>)((UT_Timer mb) => Events((MonoBehaviour)(object)mb).Any((UnityEventBase e) => Calls(e).Any((Call c) => c.target == (Object)(object)kb && c.method == "GrowTo"))));
			MonoBehaviour val = null;
			FieldPath fieldPath = null;
			if ((Object)(object)seq != (Object)null)
			{
				foreach (MonoBehaviour item in list)
				{
					if ((Object)(object)item == (Object)(object)seq)
					{
						continue;
					}
					foreach (var (ev, fieldPath2) in EventsWithPaths(item))
					{
						if (Calls(ev).Any((Call c) => c.target == (Object)(object)seq || c.target == (Object)(object)((Component)seq).gameObject))
						{
							val = item;
							fieldPath = fieldPath2;
							break;
						}
					}
					if ((Object)(object)val != (Object)null)
					{
						break;
					}
				}
			}
			SunChasePlugin.Log.LogInfo((object)("Bomb: sun " + Path(root, ((Component)kb).transform) + ", sequence " + (((Object)(object)seq != (Object)null) ? Path(root, ((Component)seq).transform) : "none") + ", button " + (((Object)(object)val != (Object)null) ? (Path(root, ((Component)val).transform) + " (" + ((object)val).GetType().Name + "." + fieldPath?.ToString() + ")") : "none")));
			HashSet<Transform> keep = new HashSet<Transform>();
			if ((Object)(object)val != (Object)null)
			{
				Transform t = (((Object)(object)((Component)val).transform.parent != (Object)null && (Object)(object)((Component)val).transform.parent != (Object)(object)root) ? ((Component)val).transform.parent : ((Component)val).transform);
				Add(t, "bomb machine");
				Add(((Component)val).transform, "button");
			}
			Add(((Component)kb).transform, "sun");
			if ((Object)(object)seq != (Object)null)
			{
				Add(((Component)seq).transform, "sequence");
			}
			List<Transform> list2 = keep.ToList();
			for (int num = 0; num < 3; num++)
			{
				if (list2.Count <= 0)
				{
					break;
				}
				List<Transform> list3 = new List<Transform>();
				foreach (Transform item2 in list2)
				{
					MonoBehaviour[] componentsInChildren = ((Component)item2).GetComponentsInChildren<MonoBehaviour>(true);
					foreach (MonoBehaviour val2 in componentsInChildren)
					{
						foreach (UnityEventBase item3 in Events(val2))
						{
							foreach (Call item4 in Calls(item3))
							{
								Object target = item4.target;
								Component val3 = (Component)(object)((target is Component) ? target : null);
								object obj2;
								if (val3 == null)
								{
									Object target2 = item4.target;
									Object obj = ((target2 is GameObject) ? target2 : null);
									obj2 = ((obj != null) ? ((GameObject)obj).transform : null);
								}
								else
								{
									obj2 = val3.transform;
								}
								Transform tt = (Transform)obj2;
								if (!((Object)(object)tt == (Object)null) && !keep.Any((Transform k) => tt.IsChildOf(k)))
								{
									Add(tt, ((Object)val2).name + "." + item4.method);
									if (keep.Contains(tt))
									{
										list3.Add(tt);
									}
								}
							}
						}
					}
				}
				list2 = list3;
			}
			holder = new GameObject("SunChase_Templates");
			holder.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)holder);
			GameObject copy = Object.Instantiate<GameObject>(level, holder.transform);
			copy.transform.SetPositionAndRotation(root.position, root.rotation);
			SunPath = IndexPath(root, ((Component)kb).transform);
			if ((Object)(object)seq != (Object)null)
			{
				SequencePath = IndexPath(root, ((Component)seq).transform);
			}
			if ((Object)(object)val != (Object)null)
			{
				ButtonPath = IndexPath(root, ((Component)val).transform);
				ButtonType = ((object)val).GetType();
				ButtonEvent = fieldPath;
			}
			HashSet<Transform> keep2 = new HashSet<Transform>(keep.Select((Transform t3) => Follow(copy.transform, IndexPath(root, t3))));
			Transform t2 = Follow(copy.transform, SunPath);
			Transform val4 = ((SequencePath != null) ? Follow(copy.transform, SequencePath) : null);
			Transform val5 = ((ButtonPath != null) ? Follow(copy.transform, ButtonPath) : null);
			Prune(copy.transform, keep2);
			foreach (Component item5 in (from c in copy.GetComponents<Component>()
				where !(c is Transform)
				select c).ToList())
			{
				Object.DestroyImmediate((Object)(object)item5);
			}
			SunPath = IndexPath(copy.transform, t2);
			if ((Object)(object)val4 != (Object)null)
			{
				SequencePath = IndexPath(copy.transform, val4);
			}
			if ((Object)(object)val5 != (Object)null)
			{
				ButtonPath = IndexPath(copy.transform, val5);
			}
			Template = copy;
			SunChasePlugin.Log.LogInfo((object)($"Bomb module: {copy.GetComponentsInChildren<Transform>(true).Length} objects, components " + string.Join(", ", (from c in copy.GetComponentsInChildren<Component>(true)
				select ((object)c).GetType().Name).Distinct())));
			return true;
			void Add(Transform val6, string why)
			{
				if (!((Object)(object)val6 == (Object)null) && !((Object)(object)val6 == (Object)(object)root) && val6.IsChildOf(root) && !keep.Any((Transform k) => val6.IsChildOf(k)))
				{
					int num3 = ((Component)val6).GetComponentsInChildren<Transform>(true).Length;
					if (num3 > 400)
					{
						SunChasePlugin.Log.LogInfo((object)$"Bomb: skipped {Path(root, val6)} ({num3} objects, {why})");
					}
					else
					{
						keep.RemoveWhere((Transform k) => k.IsChildOf(val6));
						keep.Add(val6);
						SunChasePlugin.Log.LogInfo((object)$"Bomb: keep {Path(root, val6)} ({num3} objects, {why})");
					}
				}
			}
		}

		private static void Prune(Transform t, HashSet<Transform> keep)
		{
			for (int num = t.childCount - 1; num >= 0; num--)
			{
				Transform c = t.GetChild(num);
				if (!keep.Contains(c))
				{
					if (keep.Any((Transform k) => k.IsChildOf(c)))
					{
						foreach (Component item in (from x in ((Component)c).GetComponents<Component>()
							where !(x is Transform)
							orderby (!(x is MonoBehaviour)) ? 1 : 0
							select x).ToList())
						{
							try
							{
								Object.DestroyImmediate((Object)(object)item);
							}
							catch
							{
							}
						}
						Prune(c, keep);
					}
					else
					{
						Object.DestroyImmediate((Object)(object)((Component)c).gameObject);
					}
				}
			}
		}

		public static IEnumerable<Call> Calls(UnityEventBase ev)
		{
			if (!(Traverse.Create((object)ev).Field("m_PersistentCalls").Field("m_Calls")
				.GetValue() is IList list))
			{
				yield break;
			}
			foreach (object item in list)
			{
				Traverse val = Traverse.Create(item);
				yield return new Call
				{
					target = val.Field("m_Target").GetValue<Object>(),
					method = val.Field("m_MethodName").GetValue<string>()
				};
			}
		}

		public static IEnumerable<UnityEventBase> Events(MonoBehaviour mb)
		{
			return from x in EventsWithPaths(mb)
				select x.Item1;
		}

		public static IEnumerable<(UnityEventBase, FieldPath)> EventsWithPaths(MonoBehaviour mb)
		{
			List<(UnityEventBase, FieldPath)> list = new List<(UnityEventBase, FieldPath)>();
			Type type = ((object)mb).GetType();
			while (type != null && type != typeof(MonoBehaviour))
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					try
					{
						if (typeof(UnityEventBase).IsAssignableFrom(fieldInfo.FieldType))
						{
							object? value = fieldInfo.GetValue(mb);
							UnityEventBase val = (UnityEventBase)((value is UnityEventBase) ? value : null);
							if (val != null)
							{
								list.Add((val, new FieldPath
								{
									field = fieldInfo
								}));
							}
						}
						else
						{
							if (!typeof(IList).IsAssignableFrom(fieldInfo.FieldType) || !fieldInfo.FieldType.IsGenericType || !fieldInfo.FieldType.GetGenericArguments()[0].IsClass || typeof(Object).IsAssignableFrom(fieldInfo.FieldType.GetGenericArguments()[0]) || !(fieldInfo.GetValue(mb) is IList list2))
							{
								continue;
							}
							List<FieldInfo> list3 = (from x in fieldInfo.FieldType.GetGenericArguments()[0].GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
								where typeof(UnityEventBase).IsAssignableFrom(x.FieldType)
								select x).ToList();
							if (list3.Count == 0)
							{
								continue;
							}
							for (int num = 0; num < list2.Count; num++)
							{
								foreach (FieldInfo item in list3)
								{
									if (list2[num] != null)
									{
										object? value2 = item.GetValue(list2[num]);
										UnityEventBase val2 = (UnityEventBase)((value2 is UnityEventBase) ? value2 : null);
										if (val2 != null)
										{
											list.Add((val2, new FieldPath
											{
												field = fieldInfo,
												index = num,
												inner = item
											}));
										}
									}
								}
							}
							continue;
						}
					}
					catch
					{
					}
				}
				type = type.BaseType;
			}
			return list;
		}

		public static int[] IndexPath(Transform root, Transform t)
		{
			List<int> list = new List<int>();
			Transform val = t;
			while ((Object)(object)val != (Object)(object)root)
			{
				list.Insert(0, val.GetSiblingIndex());
				val = val.parent;
			}
			return list.ToArray();
		}

		public static Transform Follow(Transform root, int[] path)
		{
			Transform val = root;
			foreach (int num in path)
			{
				val = val.GetChild(num);
			}
			return val;
		}

		private static string Path(Transform root, Transform t)
		{
			List<string> list = new List<string>();
			Transform val = t;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
			{
				list.Insert(0, ((Object)val).name);
				val = val.parent;
			}
			return string.Join("/", list);
		}
	}
	internal class BombRunner : MonoBehaviour
	{
		private DEN_DeathFloor mass;

		private Transform sun;

		private bool placed;

		private float placedAt;

		private float fxLogAt = 20f;

		public static float CurrentSpeed;

		private ENV_KillBubble kb;

		private float vanillaSpeed = 2f;

		private float loggedRun = -1f;

		private static readonly FieldRef<ENV_KillBubble, float> Scale = AccessTools.FieldRefAccess<ENV_KillBubble, float>("scale");

		private static readonly FieldRef<DEN_DeathFloor, float> MassSpeedMult = AccessTools.FieldRefAccess<DEN_DeathFloor, float>("speedMult");

		private static readonly FieldRef<DEN_DeathFloor, float> MassFrameMult = AccessTools.FieldRefAccess<DEN_DeathFloor, float>("speedMultFrameStorage");

		private static readonly FieldRef<DEN_DeathFloor, float> MassRampMult = AccessTools.FieldRefAccess<DEN_DeathFloor, float>("speedIncreaseRateMultiplier");

		private static readonly FieldRef<DEN_DeathFloor, bool> MassRubberband = AccessTools.FieldRefAccess<DEN_DeathFloor, bool>("rubberbandActive");

		private static readonly FieldRef<DEN_DeathFloor, bool> MassActive = AccessTools.FieldRefAccess<DEN_DeathFloor, bool>("active");

		private bool massStarted;

		private bool caughtUp;

		private float virtualRise;

		private const float BurstRate = 1.5f;

		private static readonly FieldRef<ENT_Player, float> PlayerSpeedMult = AccessTools.FieldRefAccess<ENT_Player, float>("speedMult");

		private static readonly FieldRef<ENT_Player, float> TempFriction = AccessTools.FieldRefAccess<ENT_Player, float>("tempFrictionMult");

		public static void Make(Vector3 massPos, DEN_DeathFloor mass)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SunChase_Bomb");
			val.transform.position = massPos;
			if ((Object)(object)WorldLoader.instance != (Object)null)
			{
				val.transform.SetParent(WorldLoader.GetWorldLoaderNullParent(), true);
			}
			val.AddComponent<BombRunner>().mass = mass;
		}

		private void Update()
		{
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			if (!placed)
			{
				if (Bomb.Done)
				{
					if (Bomb.State != Bomb.Status.Found || (Object)(object)Bomb.Template == (Object)null)
					{
						SunChasePlugin.GiveBack(mass);
						Object.Destroy((Object)(object)((Component)this).gameObject);
					}
					else
					{
						placed = true;
						placedAt = Time.time;
						Place();
					}
				}
				return;
			}
			if ((Object)(object)sun != (Object)null && (Object)(object)((Component)this).transform.parent != (Object)null && (Object)(object)sun.parent != (Object)(object)((Component)this).transform.parent)
			{
				sun.SetParent(((Component)this).transform.parent, true);
			}
			PlaceMass();
			SetSpeed();
			if (!(fxLogAt > 0f) || !(Time.time - placedAt > fxLogAt) || !((Object)(object)sun != (Object)null))
			{
				return;
			}
			fxLogAt = -1f;
			ENT_Player playerObject = ENT_Player.playerObject;
			foreach (FX_Zone item in ((Component)this).GetComponentsInChildren<FX_Zone>(true).Concat(((Component)sun).GetComponentsInChildren<FX_Zone>(true)).Distinct())
			{
				SunChasePlugin.Log.LogInfo((object)($"Bomb FX: {((Object)item).name} blend {item.blend:0.00}, range {item.minRange}-{item.maxRange}, active {((Behaviour)item).isActiveAndEnabled}, " + (((Object)(object)playerObject != (Object)null) ? (Vector3.Distance(((Component)playerObject).transform.position, ((Component)item).transform.position).ToString("0") + " m from you") : "")));
			}
		}

		private void SetSpeed()
		{
			ENT_Player playerObject = ENT_Player.playerObject;
			if (!((Object)(object)kb == (Object)null) && !((Object)(object)playerObject == (Object)null))
			{
				string text = SunChasePlugin.Normalize(SunChasePlugin.SpeedMode.Value.Split(' ')) ?? "mass_logic";
				float result;
				float num = (text.StartsWith("player ") ? PlayerSpeed(playerObject, text.Substring(7)) : ((text == "mass_logic") ? MassLogic(playerObject) : ((!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) ? vanillaSpeed : result)));
				kb.growSpeed = num;
				CurrentSpeed = num;
			}
		}

		private float RealGap(ENT_Player p)
		{
			//IL_0006: 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)
			return Mathf.Max(Vector3.Distance(((Component)p).transform.position, ((Component)kb).transform.position) - Scale.Invoke(kb), 0f);
		}

		private float VirtualGap(ENT_Player p)
		{
			//IL_0006: 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)
			return Mathf.Max(((Component)p).transform.position.y - (((Component)kb).transform.position.y + virtualRise), 0f);
		}

		private float Gap(ENT_Player p)
		{
			if (!caughtUp)
			{
				return VirtualGap(p);
			}
			return RealGap(p);
		}

		private void PlaceMass()
		{
			//IL_0094: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_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)
			if (!((Object)(object)mass == (Object)null) && !((Object)(object)sun == (Object)null))
			{
				ENT_Player playerObject = ENT_Player.playerObject;
				bool flag = (Object)(object)kb != (Object)null && (Object)(object)playerObject != (Object)null && SunChasePlugin.Normalize(SunChasePlugin.SpeedMode.Value.Split(' ')) == "mass_logic";
				((Component)mass).transform.position = (flag ? (((Component)playerObject).transform.position - Vector3.up * Gap(playerObject)) : (sun.position - Vector3.up * 100000f));
			}
		}

		private float MassLogic(ENT_Player p)
		{
			float num = Gap(p);
			float num2 = RealGap(p);
			if ((Object)(object)mass == (Object)null)
			{
				return vanillaSpeed * Mathf.Max(1f, num * 0.07f);
			}
			if (!MassActive.Invoke(mass))
			{
				return 0f;
			}
			if ((Object)(object)CL_GameManager.gMan != (Object)null && CL_GameManager.gMan.isPaused)
			{
				return 0f;
			}
			if (!massStarted && (!mass.usePlayerStartHeight || CL_GameManager.GetPlayerTravelDistance() > mass.playerStartHeight))
			{
				massStarted = true;
			}
			if (!massStarted)
			{
				return 0f;
			}
			float num3 = 1f;
			float num4 = 1f;
			if ((Object)(object)CL_GameManager.gamemode != (Object)null)
			{
				num3 = CL_GameManager.gamemode.gooSpeedIncreaseMult;
				num4 = CL_GameManager.gamemode.gooSpeedMult;
			}
			if (CL_GameManager.IsHardmode())
			{
				num4 *= 2f;
			}
			float num5 = MassSpeedMult.Invoke(mass) * num4 * MassFrameMult.Invoke(mass);
			EntityBuff curBuffs = ((GameEntity)p).curBuffs;
			if (curBuffs != null)
			{
				num5 = num5 / (1f + curBuffs.GetBuff("massResist")) * (1f + curBuffs.GetBuff("massSpeedMult")) / (1f + curBuffs.GetBuff("massSlow"));
			}
			if (WorldLoader.initialized && WorldLoader.isLoaded && WorldLoader.instance.GetCurrentLevel() != null)
			{
				M_Level level = WorldLoader.instance.GetCurrentLevel().GetLevel();
				num5 *= level.massSpeedMult;
				if ((Object)(object)level.subRegion != (Object)null)
				{
					num5 *= level.subRegion.massSpeedMult;
				}
			}
			float num6 = (MassRubberband.Invoke(mass) ? (num * 0.07f * mass.rubberbandMult) : 1f);
			float num7 = Mathf.Max(mass.speed * num6, mass.speed) * num5;
			if (num > 300f)
			{
				num7 *= 4f;
			}
			if (CL_GameManager.GetPlayerTravelDistance() > mass.playerEaseHeight)
			{
				float num8 = mass.maxSpeed;
				float num9 = 1f;
				if ((Object)(object)CL_GameManager.GetBaseGamemode() != (Object)null && CL_GameManager.GetBaseGamemode().HasTag("endless"))
				{
					float playerBestTravelDistance = CL_GameManager.gMan.GetPlayerBestTravelDistance();
					if (playerBestTravelDistance > 10000f)
					{
						num8 = mass.maxSpeed * 100f;
						num9 = 5f;
					}
					else if (playerBestTravelDistance > 5000f)
					{
						num8 = mass.maxSpeed * 1.5f;
						num9 = 1.3f;
					}
				}
				if (mass.speed < num8)
				{
					DEN_DeathFloor obj = mass;
					obj.speed += Time.deltaTime * mass.speedIncreaseRate * MassRampMult.Invoke(mass) * num3 * ((num5 < 0.5f) ? num5 : 1f) * num9;
				}
			}
			if (!float.IsFinite(num7))
			{
				num7 = 0f;
			}
			if (!caughtUp)
			{
				virtualRise += num7 * Time.deltaTime;
				if (!(Scale.Invoke(kb) > 0.01f) || !(num2 <= num + 0.5f))
				{
					return Mathf.Max(num7, 0f) + Mathf.Max(num2 - num, 0f) * 1.5f;
				}
				caughtUp = true;
				SunChasePlugin.Log.LogInfo((object)$"mass_logic: sun caught up with where the mass would be ({num:0.0} m below you)");
			}
			return Mathf.Max(num7, 0f);
		}

		private float PlayerSpeed(ENT_Player p, string how)
		{
			float num = PlayerSpeedMult.Invoke(p);
			EntityBuff curBuffs = ((GameEntity)p).curBuffs;
			float num2 = ((curBuffs == null) ? 1f : ((curBuffs.GetBuff("addSpeed") + 1f + curBuffs.GetBuff("addGroundedSpeed")) / (1f + curBuffs.GetBuff("addSlow")) / (1f + curBuffs.GetBuff("divideSpeed"))));
			float num3 = ((how == "run") ? (p.sprintSpeed * num) : ((how == "crouch") ? (p.crouchSpeed * num) : p.speed)) * num * Mathf.Max(num2, 0f) * 0.15f;
			float num4 = p.dragCoefficient * TempFriction.Invoke(p) * 15f * (1f + ((curBuffs != null) ? curBuffs.GetBuff("addDrag") : 0f));
			float num5 = ((num4 > 0f) ? (Mathf.Sqrt(num3 / num4) * 175f) : vanillaSpeed);
			if (!float.IsFinite(num5) || num5 < 0.3f || num5 > 40f)
			{
				if (loggedRun != -2f)
				{
					loggedRun = -2f;
					SunChasePlugin.Log.LogWarning((object)$"Sun player speed: {how} worked out as {num5:0.00} m/s, not believable; using the Rift's {vanillaSpeed} m/s");
				}
				return vanillaSpeed;
			}
			if (Mathf.Abs(num5 - loggedRun) > 0.05f)
			{
				loggedRun = num5;
				SunChasePlugin.Log.LogInfo((object)$"Sun player speed: your {how} = {num5:0.00} m/s");
			}
			return num5;
		}

		private void Place()
		{
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: 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_024b: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(Bomb.Template, ((Component)this).transform);
			sun = Bomb.Follow(val.transform, Bomb.SunPath);
			kb = ((Component)sun).GetComponent<ENV_KillBubble>();
			if ((Object)(object)kb != (Object)null)
			{
				vanillaSpeed = kb.growSpeed;
			}
			if ((Object)(object)mass != (Object)null)
			{
				SunChasePlugin.Log.LogInfo((object)$"Mass numbers for mass_logic: speed {mass.speed}, max {mass.maxSpeed}, ramp {mass.speedIncreaseRate}/s after {mass.playerEaseHeight} m, catch-up x{mass.rubberbandMult}");
			}
			Transform transform = val.transform;
			transform.position += ((Component)this).transform.position - sun.position;
			bool flag = false;
			if (Bomb.ButtonPath != null)
			{
				Component component = ((Component)Bomb.Follow(val.transform, Bomb.ButtonPath)).GetComponent(Bomb.ButtonType);
				UnityEventBase val2 = (((Object)(object)component != (Object)null) ? Bomb.ButtonEvent.Get(component) : null);
				MethodInfo methodInfo = ((object)val2)?.GetType().GetMethods().FirstOrDefault((MethodInfo m) => m.Name == "Invoke");
				if (methodInfo != null)
				{
					object[] parameters = (from p in methodInfo.GetParameters()
						select (!p.ParameterType.IsValueType) ? null : Activator.CreateInstance(p.ParameterType)).ToArray();
					try
					{
						methodInfo.Invoke(val2, parameters);
						flag = true;
					}
					catch (Exception ex)
					{
						SunChasePlugin.Log.LogWarning((object)("Bomb press: " + (ex.InnerException?.Message ?? ex.Message)));
					}
				}
			}
			if (!flag && Bomb.SequencePath != null)
			{
				UT_Timer component2 = ((Component)Bomb.Follow(val.transform, Bomb.SequencePath)).GetComponent<UT_Timer>();
				if ((Object)(object)component2 != (Object)null)
				{
					component2.Play(false);
					flag = true;
				}
			}
			SunChasePlugin.Log.LogInfo((object)($"Bomb placed at {((Component)this).transform.position}, " + (flag ? "pressed" : "NOT pressed (no button or sequence found)")));
		}
	}
}