Decompiled source of TimeBomb v1.0.0

TimeBomb.dll

Decompiled 14 minutes ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using BoplFixedMath;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;

[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("TimeBomb")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A Bopl Battle ability: a hot-potato time bomb you pass by tagging other players.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+bba3efb895eff9f5b64d6beae5d42bd76d687b8c")]
[assembly: AssemblyProduct("TimeBomb")]
[assembly: AssemblyTitle("TimeBomb")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 TimeBomb
{
	public static class AbilityApiClonesFix
	{
		private const string Culprit = "AbilityApi";

		private static bool done;

		public static void Ensure()
		{
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Expected O, but got Unknown
			if (done)
			{
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(PlayerCollision), "killPlayer", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				done = true;
				Plugin.Log.LogWarning((object)"Time Bomb: could not find PlayerCollision.killPlayer, so the Ability API duplication fix could not be applied. Duplicated players may lose two lives per death.");
				return;
			}
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
			if (patchInfo == null || patchInfo.Prefixes == null || patchInfo.Prefixes.Count == 0)
			{
				return;
			}
			List<MethodInfo> list = new List<MethodInfo>();
			foreach (Patch prefix in patchInfo.Prefixes)
			{
				MethodInfo patchMethod = prefix.PatchMethod;
				if (!(patchMethod == null) && !(patchMethod.DeclaringType == null) && patchMethod.DeclaringType.FullName.Contains("AbilityApi"))
				{
					list.Add(patchMethod);
				}
			}
			if (list.Count == 0)
			{
				done = true;
				Plugin.Log.LogInfo((object)"Time Bomb: no Ability API patch on killPlayer to undo; duplicated players are the game's own business.");
				return;
			}
			Harmony val = new Harmony("timebomb.abilityapi.clonefix");
			foreach (MethodInfo item in list)
			{
				val.Unpatch((MethodBase)methodInfo, item);
				Plugin.Log.LogWarning((object)("Time Bomb: removed " + item.DeclaringType.FullName + "." + item.Name + " from PlayerCollision.killPlayer. It took a second life from duplicated players on every death, which ended rounds early and swallowed wins."));
			}
			done = true;
		}
	}
	internal static class BombSpeed
	{
		private static int boostedId = -1;

		private static Fix originalSpeed;

		public static void Apply(int carrierId, bool critical)
		{
			//IL_0045: 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_003f: Unknown result type (might be due to invalid IL or missing references)
			if (!critical || carrierId < 0)
			{
				Clear();
				return;
			}
			if (boostedId != carrierId)
			{
				Clear();
			}
			PlayerPhysics val = PhysicsOf(carrierId);
			if (!((Object)(object)val == (Object)null))
			{
				if (boostedId != carrierId)
				{
					boostedId = carrierId;
					originalSpeed = val.Speed;
				}
				val.Speed = TimeBombAbility.CriticalSpeed;
			}
		}

		public static void Clear()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (boostedId >= 0)
			{
				PlayerPhysics val = PhysicsOf(boostedId);
				if ((Object)(object)val != (Object)null)
				{
					val.Speed = originalSpeed;
				}
				else
				{
					Plugin.Log.LogInfo((object)($"Time Bomb: player {boostedId} was boosted but their body is " + "gone, so there was nothing to restore."));
				}
				boostedId = -1;
			}
		}

		private static PlayerPhysics PhysicsOf(int playerId)
		{
			PlayerPhysics[] array = Object.FindObjectsOfType<PlayerPhysics>();
			foreach (PlayerPhysics val in array)
			{
				if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled)
				{
					IPlayerIdHolder component = ((Component)val).GetComponent<IPlayerIdHolder>();
					if (component != null && component.GetPlayerId() == playerId)
					{
						return val;
					}
				}
			}
			return null;
		}
	}
	public static class BombState
	{
		private static int carrierId = -1;

		private static int ticksLeft;

		private static int passImmunityTicks;

		private static readonly HashSet<long> spent = new HashSet<long>();

		private static Explosion explosionPrefab;

		public static bool HasCarrier => carrierId >= 0;

		public static int CarrierId => carrierId;

		public static int TicksLeft => ticksLeft;

		public static int SecondsLeft
		{
			get
			{
				if (ticksLeft <= 0)
				{
					return 0;
				}
				return Mathf.Clamp((ticksLeft + 60 - 1) / 60, 0, 8);
			}
		}

		public static bool IsCritical
		{
			get
			{
				if (carrierId >= 0 && ticksLeft > 0)
				{
					return ticksLeft <= 180;
				}
				return false;
			}
		}

		public static void SetExplosionPrefab(Explosion prefab)
		{
			explosionPrefab = prefab;
		}

		public static long SlotKey(int playerId, int abilityIndex)
		{
			return (long)playerId * 16L + abilityIndex;
		}

		public static bool IsSpent(long key)
		{
			return spent.Contains(key);
		}

		public static void Arm(int playerId, long slotKey)
		{
			carrierId = playerId;
			ticksLeft = 480;
			passImmunityTicks = 36;
			spent.Add(slotKey);
			Plugin.Log.LogInfo((object)($"Time Bomb: player {playerId} armed the bomb " + $"({480} ticks / 8s)."));
		}

		public static void Tick()
		{
			if (carrierId < 0)
			{
				BombSpeed.Clear();
				return;
			}
			BombSpeed.Apply(carrierId, IsCritical);
			PlayerHandler val = PlayerHandler.Get();
			if (val == null)
			{
				return;
			}
			Player player = val.GetPlayer(carrierId);
			if (player == null || !player.IsAlive)
			{
				Plugin.Log.LogInfo((object)$"Time Bomb: carrier {carrierId} is gone, so the bomb was defused.");
				Clear();
				return;
			}
			if (passImmunityTicks > 0)
			{
				passImmunityTicks--;
			}
			else
			{
				TryPassBomb(val, player);
			}
			ticksLeft--;
			if (ticksLeft <= 0)
			{
				Detonate(player);
			}
		}

		private static void TryPassBomb(PlayerHandler handler, Player carrier)
		{
			//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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: 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)
			Fix radiusSquared = TimeBombAbility.TagRadius * TimeBombAbility.TagRadius;
			List<Vec2> list = PlayerBodies.PositionsOf(carrierId);
			if (list.Count == 0)
			{
				list.Add(carrier.Position);
			}
			foreach (Player item in handler.PlayerList())
			{
				if (item != null && item.IsAlive && item.Id != carrierId)
				{
					List<Vec2> list2 = PlayerBodies.PositionsOf(item.Id);
					if (list2.Count == 0)
					{
						list2.Add(item.Position);
					}
					if (AnyWithin(list, list2, radiusSquared))
					{
						int num = carrierId;
						carrierId = item.Id;
						ticksLeft = 480;
						passImmunityTicks = 36;
						Plugin.Log.LogInfo((object)($"Time Bomb: player {num} passed the bomb to player {item.Id}; " + "fuse reset to 8s."));
						break;
					}
				}
			}
		}

		private static bool AnyWithin(List<Vec2> mine, List<Vec2> theirs, Fix radiusSquared)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			foreach (Vec2 item in mine)
			{
				foreach (Vec2 their in theirs)
				{
					if (Vec2.SqrMagnitude(their - item) <= radiusSquared)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static void Detonate(Player carrier)
		{
			//IL_0001: 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_0097: Unknown result type (might be due to invalid IL or missing references)
			Vec2 position = carrier.Position;
			int num = carrierId;
			Clear();
			int bodiesSeen;
			int num2 = PlayerBodies.KillAll(num, num, (CauseOfDeath)0, out bodiesSeen, respectProtection: false);
			if (num2 == 0)
			{
				PlayerBodies.WarnIfBlind(bodiesSeen, "Time Bomb");
				Plugin.Log.LogWarning((object)($"Time Bomb: the fuse ran out on player {num} but none of " + $"their {bodiesSeen} bodies could be killed, so they are " + "relying on the blast to catch them."));
			}
			if ((Object)(object)explosionPrefab == (Object)null)
			{
				Plugin.Log.LogWarning((object)($"Time Bomb: no explosion prefab available, so player {num} " + "was not caught in a blast."));
				return;
			}
			Explosion val = FixTransform.InstantiateFixed<Explosion>(explosionPrefab, position);
			if ((Object)(object)val != (Object)null)
			{
				val.PlayerOwnerId = num;
			}
			Plugin.Log.LogInfo((object)($"Time Bomb: the fuse ran out on player {num} " + $"({num2} of {bodiesSeen} bodies killed outright)."));
		}

		public static void Clear()
		{
			BombSpeed.Clear();
			carrierId = -1;
			ticksLeft = 0;
			passImmunityTicks = 0;
		}

		public static void ResetForNextStage()
		{
			Clear();
			spent.Clear();
		}
	}
	public static class BombVisuals
	{
		private static Sprite[] digits;

		private static bool loadAttempted;

		private static bool loadFailed;

		private static GameObject counter;

		private static SpriteRenderer counterRenderer;

		private static int shownDigit = -1;

		private static Sprite[] bombFrames;

		private static GameObject heldBomb;

		private static SpriteRenderer heldBombRenderer;

		private static float fusePhase;

		public static void Sync()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: 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_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: 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)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			if (loadFailed || (!loadAttempted && !LoadDigits()))
			{
				return;
			}
			if (!BombState.HasCarrier)
			{
				Hide();
				return;
			}
			SpriteRenderer val = FindPlayerSprite(BombState.CarrierId);
			if ((Object)(object)val == (Object)null)
			{
				Hide();
				return;
			}
			if ((Object)(object)counter == (Object)null)
			{
				counter = new GameObject("TimeBombCounter");
				counterRenderer = counter.AddComponent<SpriteRenderer>();
				shownDigit = -1;
			}
			if ((Object)(object)heldBomb == (Object)null && bombFrames != null)
			{
				heldBomb = new GameObject("TimeBombHeld");
				heldBombRenderer = heldBomb.AddComponent<SpriteRenderer>();
			}
			counter.SetActive(true);
			Quaternion rotation = ((Component)val).transform.rotation;
			float num = PlayerScale();
			counter.transform.position = ((Component)val).transform.position + rotation * (Vector3.up * 2.1f * num);
			counter.transform.localScale = new Vector3(num, num, 1f);
			((Renderer)counterRenderer).sortingLayerID = ((Renderer)val).sortingLayerID;
			((Renderer)counterRenderer).sortingOrder = ((Renderer)val).sortingOrder + 2;
			counterRenderer.color = (BombState.IsCritical ? TimeBombAbility.CriticalTint : Color.white);
			if ((Object)(object)heldBomb != (Object)null)
			{
				heldBomb.SetActive(true);
				float num2 = FacingSign(BombState.CarrierId);
				heldBomb.transform.position = ((Component)val).transform.position + rotation * new Vector3(0.8f * num2 * num, 0.3f * num, 0f);
				heldBomb.transform.rotation = rotation;
				float num3 = Pulse();
				heldBomb.transform.localScale = new Vector3(num2 * num * num3, num * num3, 1f);
				((Renderer)heldBombRenderer).sortingLayerID = ((Renderer)val).sortingLayerID;
				((Renderer)heldBombRenderer).sortingOrder = ((Renderer)val).sortingOrder + 1;
				heldBombRenderer.sprite = bombFrames[NextFuseFrame()];
			}
			int secondsLeft = BombState.SecondsLeft;
			if (secondsLeft != shownDigit)
			{
				shownDigit = secondsLeft;
				counterRenderer.sprite = digits[Mathf.Clamp(secondsLeft, 0, digits.Length - 1)];
			}
		}

		public static void Hide()
		{
			if ((Object)(object)counter != (Object)null)
			{
				counter.SetActive(false);
			}
			if ((Object)(object)heldBomb != (Object)null)
			{
				heldBomb.SetActive(false);
			}
			shownDigit = -1;
		}

		public static void Destroy()
		{
			if ((Object)(object)counter != (Object)null)
			{
				Object.Destroy((Object)(object)counter);
			}
			if ((Object)(object)heldBomb != (Object)null)
			{
				Object.Destroy((Object)(object)heldBomb);
			}
			counter = null;
			counterRenderer = null;
			heldBomb = null;
			heldBombRenderer = null;
			shownDigit = -1;
		}

		private static float Urgency()
		{
			return 1f - Mathf.Clamp01((float)BombState.TicksLeft / 480f);
		}

		private static float Pulse()
		{
			int ticksLeft = BombState.TicksLeft;
			if (ticksLeft <= 0)
			{
				return 1f;
			}
			int num = ((ticksLeft <= 180) ? 30 : 60);
			float num2 = (float)(num - ticksLeft % num) / (float)num;
			float num3 = Mathf.Lerp(0.07f, 0.24f, Urgency());
			return 1f + num3 * Mathf.Pow(1f - num2, 5f);
		}

		private static int NextFuseFrame()
		{
			float num = Mathf.Lerp(10f, 3f, Urgency());
			fusePhase = (fusePhase + 1f / Mathf.Max(0.5f, num)) % 3f;
			return Mathf.Clamp((int)fusePhase, 0, 2);
		}

		private static float PlayerScale()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			PlayerHandler val = PlayerHandler.Get();
			Player val2 = ((val == null) ? null : val.GetPlayer(BombState.CarrierId));
			if (val2 != null)
			{
				return Mathf.Max(0.01f, (float)val2.Scale);
			}
			return 1f;
		}

		private static float FacingSign(int playerId)
		{
			SlimeController[] array = Object.FindObjectsOfType<SlimeController>();
			foreach (SlimeController val in array)
			{
				if (val.GetPlayerId() == playerId)
				{
					if (!val.isFacingRight())
					{
						return -1f;
					}
					return 1f;
				}
			}
			return 1f;
		}

		private static SpriteRenderer FindPlayerSprite(int playerId)
		{
			SlimeController[] array = Object.FindObjectsOfType<SlimeController>();
			foreach (SlimeController val in array)
			{
				if (val.GetPlayerId() == playerId)
				{
					SpriteRenderer playerSprite = val.GetPlayerSprite();
					if ((Object)(object)playerSprite != (Object)null && ((Component)playerSprite).gameObject.activeInHierarchy)
					{
						return playerSprite;
					}
				}
			}
			Ability[] array2 = Object.FindObjectsOfType<Ability>();
			foreach (Ability val2 in array2)
			{
				if (val2.GetPlayerId() == playerId)
				{
					SpriteRenderer component = ((Component)val2).GetComponent<SpriteRenderer>();
					if ((Object)(object)component != (Object)null && ((Component)component).gameObject.activeInHierarchy)
					{
						return component;
					}
				}
			}
			return null;
		}

		private static bool LoadDigits()
		{
			loadAttempted = true;
			Sprite[] array = (Sprite[])(object)new Sprite[9];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = TimeBombAbility.LoadDigit(i);
				if ((Object)(object)array[i] == (Object)null)
				{
					Plugin.Log.LogError((object)($"Time Bomb: digit {i} failed to load, so no countdown will be " + "shown. The bomb itself still works."));
					loadFailed = true;
					return false;
				}
			}
			digits = array;
			Sprite[] array2 = (Sprite[])(object)new Sprite[3];
			for (int j = 0; j < array2.Length; j++)
			{
				array2[j] = TimeBombAbility.LoadBombFrame(j);
				if ((Object)(object)array2[j] == (Object)null)
				{
					Plugin.Log.LogWarning((object)($"Time Bomb: bomb frame {j} failed to load, so the carrier " + "will show only the countdown."));
					array2 = null;
					break;
				}
			}
			bombFrames = array2;
			Plugin.Log.LogInfo((object)"Time Bomb: countdown digits ready.");
			return true;
		}
	}
	internal static class PlayerBodies
	{
		private static bool warnedEmpty;

		private static readonly HashSet<int> alreadyKilled = new HashSet<int>();

		public static void ForgetAll()
		{
			alreadyKilled.Clear();
		}

		public static bool IsProtected(int playerId)
		{
			PlayerHandler val = PlayerHandler.Get();
			Player val2 = ((val == null) ? null : val.GetPlayer(playerId));
			if (val2 != null && val2.InPlatformTransform)
			{
				return true;
			}
			ControlPlatform[] array = Object.FindObjectsOfType<ControlPlatform>();
			foreach (ControlPlatform val3 in array)
			{
				if (!((Object)(object)val3 == (Object)null) && ((Behaviour)val3).isActiveAndEnabled)
				{
					IPlayerIdHolder component = ((Component)val3).GetComponent<IPlayerIdHolder>();
					if (component != null && component.GetPlayerId() == playerId)
					{
						return true;
					}
				}
			}
			return false;
		}

		public static int KillWhere(int victimId, int killerId, CauseOfDeath causeOfDeath, Func<Vec2, bool> isInside, out int bodiesSeen, bool respectProtection = true)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			bodiesSeen = 0;
			int num = 0;
			if (respectProtection && IsProtected(victimId))
			{
				return 0;
			}
			PlayerCollision[] array = Object.FindObjectsOfType<PlayerCollision>();
			foreach (PlayerCollision val in array)
			{
				if ((Object)(object)val == (Object)null || ((MonoUpdatable)val).IsDestroyed || !((Component)val).gameObject.activeInHierarchy || val.GetPlayerId() != victimId)
				{
					continue;
				}
				FixTransform component = ((Component)val).GetComponent<FixTransform>();
				if (!((Object)(object)component == (Object)null) && !component.IsDestroyed)
				{
					bodiesSeen++;
					if (isInside(component.position) && !alreadyKilled.Contains(((Object)val).GetInstanceID()) && val.killPlayer(killerId, true, !respectProtection, causeOfDeath))
					{
						alreadyKilled.Add(((Object)val).GetInstanceID());
						num++;
					}
				}
			}
			return num;
		}

		public static int KillAll(int victimId, int killerId, CauseOfDeath causeOfDeath, out int bodiesSeen, bool respectProtection = true)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			return KillWhere(victimId, killerId, causeOfDeath, (Vec2 position) => true, out bodiesSeen, respectProtection);
		}

		public static List<Vec2> PositionsOf(int playerId)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			List<Vec2> list = new List<Vec2>();
			PlayerCollision[] array = Object.FindObjectsOfType<PlayerCollision>();
			foreach (PlayerCollision val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((MonoUpdatable)val).IsDestroyed && ((Component)val).gameObject.activeInHierarchy && val.GetPlayerId() == playerId)
				{
					FixTransform component = ((Component)val).GetComponent<FixTransform>();
					if ((Object)(object)component != (Object)null && !component.IsDestroyed)
					{
						list.Add(component.position);
					}
				}
			}
			return list;
		}

		public static void WarnIfBlind(int bodiesSeen, string what)
		{
			if (bodiesSeen <= 0 && !warnedEmpty)
			{
				warnedEmpty = true;
				Plugin.Log.LogWarning((object)(what + ": no live PlayerCollision was found for the target, so nothing can be killed. Every kill in this mod goes through that lookup, so this is not a near miss -- the scan itself is blind and the ability will appear to do nothing. Falling back to the old single-body lookup for this hit."));
			}
		}
	}
	[BepInPlugin("com.maha.boplbattle.timebomb", "Time Bomb", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.maha.boplbattle.timebomb";

		public const string PluginName = "Time Bomb";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"Time Bomb: loading...");
			new Harmony("com.maha.boplbattle.timebomb").PatchAll();
			Log.LogInfo((object)($"Time Bomb: loaded (fuse {480} ticks / 8s, " + $"cooldown {1}s, one use per round)."));
		}
	}
	public static class TimeBombAbility
	{
		public const string AbilityName = "timebomb";

		public const string TemplateNameFragment = "invis";

		public const int FuseTicks = 480;

		public const int TicksPerSecond = 60;

		public const int CooldownSeconds = 1;

		public const float IconScale = 0.85f;

		public static readonly Fix TagRadius = (Fix)1.6f;

		public const int PassImmunityTicks = 36;

		public const float DigitWorldSize = 1.6f;

		public const float DigitHeightAbovePlayer = 2.1f;

		public const float HeldBombWorldSize = 1.7f;

		public const float HeldBombHeightAbovePlayer = 0.3f;

		public const float HeldBombSideOffset = 0.8f;

		public const int BombFrames = 3;

		public const float FuseFrameTicksSlow = 10f;

		public const float FuseFrameTicksFast = 3f;

		public const int CriticalTicks = 180;

		public static readonly Fix CriticalSpeed = (Fix)23L;

		public static readonly Color CriticalTint = new Color(1f, 0.25f, 0.2f, 1f);

		public const float PulseSmall = 0.07f;

		public const float PulseLarge = 0.24f;

		public const int PulseDoubleTimeUnder = 180;

		public static bool IsTimeBomb(GameObject go)
		{
			if ((Object)(object)go != (Object)null)
			{
				return ((Object)go).name.ToLower().Contains("timebomb");
			}
			return false;
		}

		public static Sprite LoadIcon(Sprite template)
		{
			//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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = LoadTexture("TimeBomb.AbilityIcon.png");
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			float pixelsPerUnit = 100f;
			if ((Object)(object)template != (Object)null)
			{
				Rect rect = template.rect;
				if (((Rect)(ref rect)).width > 0f)
				{
					float pixelsPerUnit2 = template.pixelsPerUnit;
					float num = ((Texture)val).width;
					rect = template.rect;
					pixelsPerUnit = pixelsPerUnit2 * (num / ((Rect)(ref rect)).width) / 0.85f;
				}
			}
			return Build(val, pixelsPerUnit);
		}

		public static Sprite LoadBombFrame(int frame)
		{
			Texture2D val = LoadTexture($"TimeBomb.Bomb{frame}.png");
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			return Build(val, (float)((Texture)val).width / 1.7f);
		}

		public static Sprite LoadDigit(int digit)
		{
			Texture2D val = LoadTexture($"TimeBomb.Digit{digit}.png");
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			return Build(val, (float)((Texture)val).width / 1.6f);
		}

		private static Sprite Build(Texture2D texture, float pixelsPerUnit)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			return Sprite.Create(texture, new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
		}

		private static Texture2D LoadTexture(string resourceName)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
			if (stream == null)
			{
				Plugin.Log.LogError((object)("Time Bomb: embedded resource '" + resourceName + "' not found."));
				return null;
			}
			byte[] array;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				stream.CopyTo(memoryStream);
				array = memoryStream.ToArray();
			}
			Texture2D val = new Texture2D(1, 1);
			if (!ImageConversion.LoadImage(val, array))
			{
				Plugin.Log.LogError((object)("Time Bomb: '" + resourceName + "' failed to decode as a PNG."));
				return null;
			}
			return val;
		}
	}
	[HarmonyPatch(typeof(AbilityGrid), "Awake")]
	public static class TimeBombInjectionPatch
	{
		private static bool hasInjected;

		public static void Prefix(AbilityGrid __instance)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			if (hasInjected)
			{
				return;
			}
			NamedSpriteList abilityIcons = __instance.abilityIcons;
			if ((Object)(object)abilityIcons == (Object)null || abilityIcons.sprites == null)
			{
				Plugin.Log.LogWarning((object)"Time Bomb: AbilityGrid.abilityIcons was empty this Awake; not injecting yet (will retry on the next grid build).");
				return;
			}
			CacheExplosionPrefab(abilityIcons);
			NamedSprite val = default(NamedSprite);
			bool flag = false;
			foreach (NamedSprite sprite in abilityIcons.sprites)
			{
				if (sprite.name != null && sprite.name.ToLower().Contains("invis"))
				{
					val = sprite;
					flag = true;
					break;
				}
			}
			if (!flag || (Object)(object)val.associatedGameObject == (Object)null)
			{
				Plugin.Log.LogError((object)"Time Bomb: could not find a native instant ability to clone, so the bomb was not added. (Looked for an ability whose name contains 'invis'.)");
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(val.associatedGameObject);
			Object.DontDestroyOnLoad((Object)(object)val2);
			((Object)val2).name = "timebomb";
			StripTemplateEffect(val2);
			InstantAbility component = val2.GetComponent<InstantAbility>();
			if ((Object)(object)component == (Object)null)
			{
				Plugin.Log.LogError((object)"Time Bomb: the cloned ability has no InstantAbility component, so it cannot be cast as an instant ability.");
				return;
			}
			component.SetCoolDown((Fix)1L);
			Sprite val3 = TimeBombAbility.LoadIcon(val.sprite) ?? val.sprite;
			abilityIcons.sprites.Add(new NamedSprite("timebomb", val3, val2, true));
			hasInjected = true;
			Plugin.Log.LogInfo((object)("Time Bomb: ability injected into the select grid (cloned '" + val.name + "', " + $"cooldown {1}s, fuse 8s)."));
		}

		private static void StripTemplateEffect(GameObject clone)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			InstantAbility component = clone.GetComponent<InstantAbility>();
			if ((Object)(object)component != (Object)null)
			{
				component.funcOnEnter = new UnityEvent();
				component.isInvisibilty = false;
				component.EffectOnEnter = null;
			}
			Invisibility component2 = clone.GetComponent<Invisibility>();
			if ((Object)(object)component2 != (Object)null)
			{
				Object.Destroy((Object)(object)component2);
			}
		}

		private static void CacheExplosionPrefab(NamedSpriteList icons)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			foreach (NamedSprite sprite in icons.sprites)
			{
				if (sprite.name == null || !sprite.name.ToLower().Contains("grenade") || (Object)(object)sprite.associatedGameObject == (Object)null)
				{
					continue;
				}
				ThrowItem2 component = sprite.associatedGameObject.GetComponent<ThrowItem2>();
				if (!((Object)(object)component == (Object)null) && !((Object)(object)component.ItemPrefab == (Object)null))
				{
					GrenadeExplode component2 = ((Component)component.ItemPrefab).GetComponent<GrenadeExplode>();
					if (!((Object)(object)component2 == (Object)null) && !((Object)(object)component2.explosion == (Object)null))
					{
						BombState.SetExplosionPrefab(component2.explosion);
						Plugin.Log.LogInfo((object)"Time Bomb: borrowed the grenade's explosion prefab for the fuse blast.");
						return;
					}
				}
			}
			Plugin.Log.LogWarning((object)"Time Bomb: could not find the grenade's explosion prefab, so the fuse running out will kill nobody.");
		}
	}
	[HarmonyPatch(typeof(InstantAbility), "EnterAbility")]
	public static class TimeBombCastPatch
	{
		public static void Postfix(InstantAbility __instance)
		{
			if (TimeBombAbility.IsTimeBomb(((Component)__instance).gameObject))
			{
				int playerId = __instance.playerInfo.playerId;
				int abilityButtonUsedIndex = __instance.playerInfo.AbilityButtonUsedIndex012;
				BombState.Arm(playerId, BombState.SlotKey(playerId, abilityButtonUsedIndex));
				AudioManager.Get().Play("startEngine");
			}
		}
	}
	[HarmonyPatch(typeof(SlimeController), "isAbilityCastable")]
	public static class TimeBombOneUsePatch
	{
		public static bool Prefix(SlimeController __instance, int abilityIndex, ref bool __result)
		{
			if (__instance.abilities == null || abilityIndex < 0 || abilityIndex >= __instance.abilities.Count)
			{
				return true;
			}
			AbilityMonoBehaviour val = __instance.abilities[abilityIndex];
			if ((Object)(object)val == (Object)null || !TimeBombAbility.IsTimeBomb(((Component)val).gameObject))
			{
				return true;
			}
			if (!BombState.IsSpent(BombState.SlotKey(__instance.GetPlayerId(), abilityIndex)))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(Updater), "TickSimulation")]
	public static class TimeBombTickPatch
	{
		public static void Postfix()
		{
			AbilityApiClonesFix.Ensure();
			BombState.Tick();
			BombVisuals.Sync();
		}
	}
	[HarmonyPatch(typeof(PlayerHandler), "ResetForNextStage")]
	public static class TimeBombResetPatch
	{
		public static void Prefix()
		{
			PlayerBodies.ForgetAll();
			BombState.ResetForNextStage();
			BombVisuals.Destroy();
		}
	}
	[HarmonyPatch(typeof(Updater), "PreLevelLoad")]
	public static class TimeBombLevelLoadPatch
	{
		public static void Prefix()
		{
			BombState.ResetForNextStage();
			BombVisuals.Destroy();
		}
	}
	[HarmonyPatch(typeof(Player), "Kill")]
	public static class TimeBombDefuseOnDeathPatch
	{
		public static void Prefix(Player __instance)
		{
			if (BombState.HasCarrier && BombState.CarrierId == __instance.Id)
			{
				Plugin.Log.LogInfo((object)($"Time Bomb: carrier {__instance.Id} died, so the bomb was defused " + "rather than left ticking."));
				BombState.Clear();
			}
		}
	}
}