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 list = new List(); 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(); foreach (PlayerPhysics val in array) { if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled) { IPlayerIdHolder component = ((Component)val).GetComponent(); 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 spent = new HashSet(); 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 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 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 mine, List 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(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(); shownDigit = -1; } if ((Object)(object)heldBomb == (Object)null && bombFrames != null) { heldBomb = new GameObject("TimeBombHeld"); heldBombRenderer = heldBomb.AddComponent(); } 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(); 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(); 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(); foreach (Ability val2 in array2) { if (val2.GetPlayerId() == playerId) { SpriteRenderer component = ((Component)val2).GetComponent(); 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 alreadyKilled = new HashSet(); 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(); foreach (ControlPlatform val3 in array) { if (!((Object)(object)val3 == (Object)null) && ((Behaviour)val3).isActiveAndEnabled) { IPlayerIdHolder component = ((Component)val3).GetComponent(); if (component != null && component.GetPlayerId() == playerId) { return true; } } } return false; } public static int KillWhere(int victimId, int killerId, CauseOfDeath causeOfDeath, Func 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(); 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(); 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 PositionsOf(int playerId) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) List list = new List(); PlayerCollision[] array = Object.FindObjectsOfType(); 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(); 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(val.associatedGameObject); Object.DontDestroyOnLoad((Object)(object)val2); ((Object)val2).name = "timebomb"; StripTemplateEffect(val2); InstantAbility component = val2.GetComponent(); 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(); if ((Object)(object)component != (Object)null) { component.funcOnEnter = new UnityEvent(); component.isInvisibilty = false; component.EffectOnEnter = null; } Invisibility component2 = clone.GetComponent(); 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(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.ItemPrefab == (Object)null)) { GrenadeExplode component2 = ((Component)component.ItemPrefab).GetComponent(); 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(); } } } }