Decompiled source of ReturnByDeath v0.2.0

ReturnByDeath.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using REPOLib.Modules;
using ReturnByDeath.Domain;
using UnityEngine;
using UnityEngine.Events;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ReturnByDeath")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0")]
[assembly: AssemblyProduct("ReturnByDeath")]
[assembly: AssemblyTitle("ReturnByDeath")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.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 ReturnByDeath
{
	internal static class CerealVisual
	{
		private static Texture2D artwork;

		internal static void Apply(GameObject prefab)
		{
			//IL_00c9: 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)
			//IL_0049: Expected O, but got Unknown
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			if ((Object)(object)artwork == (Object)null)
			{
				using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ReturnByDeath.Cereal.png"))
				{
					if (stream == null)
					{
						throw new FileNotFoundException("Embedded cereal artwork missing");
					}
					using MemoryStream memoryStream = new MemoryStream();
					stream.CopyTo(memoryStream);
					artwork = new Texture2D(2, 2, (TextureFormat)4, true);
					if (!ImageConversion.LoadImage(artwork, memoryStream.ToArray()))
					{
						throw new IOException("Invalid cereal texture");
					}
				}
				((Object)artwork).name = "Return By Death Cereal Albedo";
				((Texture)artwork).wrapMode = (TextureWrapMode)1;
				((Texture)artwork).filterMode = (FilterMode)1;
				Object.DontDestroyOnLoad((Object)(object)artwork);
			}
			Transform obj = prefab.transform.Find("Mesh");
			if ((Object)(object)obj == (Object)null)
			{
				throw new MissingComponentException("Vanilla cereal mesh not found");
			}
			MeshRenderer[] componentsInChildren = ((Component)obj).GetComponentsInChildren<MeshRenderer>(true);
			foreach (MeshRenderer val in componentsInChildren)
			{
				Material[] sharedMaterials = ((Renderer)val).sharedMaterials;
				for (int j = 0; j < sharedMaterials.Length; j++)
				{
					sharedMaterials[j] = new Material(sharedMaterials[j]);
					if (sharedMaterials[j].HasProperty("_MainTex"))
					{
						sharedMaterials[j].SetTexture("_MainTex", (Texture)(object)artwork);
					}
					if (sharedMaterials[j].HasProperty("_BaseMap"))
					{
						sharedMaterials[j].SetTexture("_BaseMap", (Texture)(object)artwork);
					}
					if (sharedMaterials[j].HasProperty("_BaseColorMap"))
					{
						sharedMaterials[j].SetTexture("_BaseColorMap", (Texture)(object)artwork);
					}
				}
				((Renderer)val).sharedMaterials = sharedMaterials;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
	internal static class DeathPatch
	{
		private static void Postfix(PlayerAvatar __instance)
		{
			Plugin.Runtime?.ObserveDeath(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathDone")]
	internal static class HeadProtectionPatch
	{
		private static void Prefix(PlayerAvatar __instance)
		{
			Plugin.Runtime?.ProtectHead(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")]
	internal static class ReturnedPatch
	{
		private static void Postfix(PlayerAvatar __instance)
		{
			Plugin.Runtime?.AfterRevive(__instance);
		}
	}
	[HarmonyPatch(typeof(RunManager), "AllPlayersDeadSet")]
	internal static class HoldGameOverPatch
	{
		private static bool Prefix(bool _set)
		{
			if (_set && Plugin.Runtime != null)
			{
				return !Plugin.Runtime.HoldFailure();
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
	internal static class LevelPatch
	{
		private static bool Prefix(bool _levelFailed)
		{
			if (_levelFailed && Plugin.Runtime != null && Plugin.Runtime.HoldFailure())
			{
				return false;
			}
			Plugin.Runtime?.LevelChanging();
			return true;
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "Start")]
	internal static class LevelReadyPatch
	{
		private static void Postfix()
		{
			Plugin.Runtime?.LevelReady();
		}
	}
	[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
	internal static class NewRunPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			Plugin.Runtime?.NewRun();
		}
	}
	[BepInPlugin("sqyan.ReturnByDeath", "Return By Death", "0.2.0")]
	[BepInDependency("REPOLib", "4.3.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string UpgradeId = "ReturnByDeath";

		internal static PlayerUpgrade Upgrade;

		internal static RevivalRuntime Runtime;

		internal static Plugin Instance;

		private Harmony harmony;

		private float nextError;

		private void Awake()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			Instance = this;
			Log.Source = ((BaseUnityPlugin)this).Logger;
			ReturnAudio.Initialize(((Component)this).gameObject);
			Runtime = new RevivalRuntime();
			harmony = new Harmony("sqyan.ReturnByDeath");
			harmony.PatchAll(typeof(Plugin).Assembly);
			Log.Info("Return By Death 0.2.0 loaded. Personal permanent upgrade; one return per level; tiers 1/50/100 HP; $25,000 regular minimum.");
		}

		private void Update()
		{
			try
			{
				RevivalShop.Tick();
				Runtime.Tick();
			}
			catch (Exception ex)
			{
				if (Time.unscaledTime >= nextError)
				{
					nextError = Time.unscaledTime + 5f;
					Log.Error(ex.ToString());
				}
			}
		}

		private void OnDestroy()
		{
			Runtime?.Reset();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			Instance = null;
		}
	}
	internal static class Log
	{
		internal static ManualLogSource Source;

		internal static void Info(string value)
		{
			ManualLogSource source = Source;
			if (source != null)
			{
				source.LogInfo((object)value);
			}
		}

		internal static void Warn(string value)
		{
			ManualLogSource source = Source;
			if (source != null)
			{
				source.LogWarning((object)value);
			}
		}

		internal static void Error(string value)
		{
			ManualLogSource source = Source;
			if (source != null)
			{
				source.LogError((object)value);
			}
		}
	}
	internal static class ReturnAudio
	{
		private static AudioSource source;

		internal static void Initialize(GameObject owner)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ReturnByDeath.Return.wav");
			if (stream == null)
			{
				throw new FileNotFoundException("Return sound missing");
			}
			BinaryReader reader = new BinaryReader(stream);
			try
			{
				if (Four() != "RIFF")
				{
					throw new InvalidDataException("Expected RIFF");
				}
				reader.ReadInt32();
				if (Four() != "WAVE")
				{
					throw new InvalidDataException("Expected WAVE");
				}
				int num = 1;
				int num2 = 44100;
				int num3 = 16;
				byte[] array = null;
				while (stream.Position + 8 <= stream.Length)
				{
					string text = Four();
					int num4 = reader.ReadInt32();
					long position = stream.Position + num4 + (num4 & 1);
					if (text == "fmt ")
					{
						if (reader.ReadInt16() != 1)
						{
							throw new InvalidDataException("PCM WAV required");
						}
						num = reader.ReadInt16();
						num2 = reader.ReadInt32();
						reader.ReadInt32();
						reader.ReadInt16();
						num3 = reader.ReadInt16();
					}
					else if (text == "data")
					{
						array = reader.ReadBytes(num4);
					}
					stream.Position = position;
				}
				if (array == null || num3 != 16)
				{
					throw new InvalidDataException("16-bit WAV data required");
				}
				float[] array2 = new float[array.Length / 2];
				for (int i = 0; i < array2.Length; i++)
				{
					array2[i] = (float)BitConverter.ToInt16(array, i * 2) / 32768f;
				}
				AudioClip val = AudioClip.Create("Return By Death", array2.Length / num, num, num2, false);
				val.SetData(array2, 0);
				source = owner.AddComponent<AudioSource>();
				source.clip = val;
				source.spatialBlend = 0f;
				source.volume = 0.65f;
				source.playOnAwake = false;
			}
			finally
			{
				if (reader != null)
				{
					((IDisposable)reader).Dispose();
				}
			}
			string Four()
			{
				return new string(reader.ReadChars(4));
			}
		}

		internal static void Play()
		{
			if ((Object)(object)source != (Object)null)
			{
				source.Stop();
				source.Play();
			}
		}
	}
	internal sealed class RevivalRuntime
	{
		private sealed class Pending
		{
			internal PlayerAvatar Player;

			internal int Level;

			internal float Due;

			internal float Deadline;
		}

		private const string UsageKey = "returnByDeathUsedOnLevel";

		private readonly ReturnCharge charges = new ReturnCharge();

		private readonly Dictionary<string, Pending> pending = new Dictionary<string, Pending>();

		private readonly Dictionary<string, Vector3> armed = new Dictionary<string, Vector3>();

		private readonly Dictionary<string, float> snapUntil = new Dictionary<string, float>();

		private readonly List<string> keys = new List<string>();

		private readonly NetworkedEvent action;

		private bool transitioning;

		private int CurrentLevel
		{
			get
			{
				if (!((Object)(object)StatsManager.instance != (Object)null) || !StatsManager.instance.runStats.TryGetValue("level", out var value))
				{
					return -1;
				}
				return value;
			}
		}

		private bool IsHost
		{
			get
			{
				if ((Object)(object)GameManager.instance != (Object)null)
				{
					return SemiFunc.IsMasterClientOrSingleplayer();
				}
				return false;
			}
		}

		internal RevivalRuntime()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			action = new NetworkedEvent("Return By Death v1", (Action<EventData>)Receive);
		}

		private void BindUsage()
		{
			StatsManager instance = StatsManager.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if (instance.dictionaryOfDictionaries.TryGetValue("returnByDeathUsedOnLevel", out var value))
				{
					charges.UsedOnLevel = value;
				}
				else
				{
					instance.dictionaryOfDictionaries["returnByDeathUsedOnLevel"] = charges.UsedOnLevel;
				}
			}
		}

		internal void NewRun()
		{
			Reset();
			charges.UsedOnLevel = new Dictionary<string, int>();
			if ((Object)(object)StatsManager.instance != (Object)null)
			{
				StatsManager.instance.dictionaryOfDictionaries["returnByDeathUsedOnLevel"] = charges.UsedOnLevel;
			}
		}

		internal void ObserveDeath(PlayerAvatar player)
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			if (!IsHost || transitioning || (Object)(object)player == (Object)null || !player.deadSet || !SemiFunc.RunIsLevel() || CurrentLevel < 0)
			{
				return;
			}
			TruckSafetySpawnPoint instance = TruckSafetySpawnPoint.instance;
			if (!((Object)(object)instance == (Object)null) && Plugin.Upgrade != null)
			{
				BindUsage();
				if (charges.TryReserve(player.steamID, CurrentLevel, Plugin.Upgrade.GetLevel(player) > 0))
				{
					pending[player.steamID] = new Pending
					{
						Player = player,
						Level = CurrentLevel,
						Due = Time.unscaledTime + 0.75f,
						Deadline = Time.unscaledTime + 4f
					};
					Dispatch("arm", player, ((Component)instance).transform.position);
					Log.Info("Return armed for an upgraded player; one charge reserved for this level.");
				}
			}
		}

		internal bool HoldFailure()
		{
			if (!IsHost)
			{
				return false;
			}
			foreach (Pending value in pending.Values)
			{
				if ((Object)(object)value.Player != (Object)null && value.Player.deadSet && Time.unscaledTime < value.Deadline)
				{
					return true;
				}
			}
			return false;
		}

		internal void ProtectHead(PlayerAvatar player)
		{
			//IL_0024: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player != (Object)null && armed.TryGetValue(player.steamID, out var value))
			{
				player.playerAvatarCollision.deathHeadPosition = value + Vector3.up * 0.25f;
			}
		}

		internal void Tick()
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)GameManager.instance == (Object)null)
			{
				return;
			}
			BindUsage();
			keys.Clear();
			keys.AddRange(snapUntil.Keys);
			foreach (string key in keys)
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(key);
				Vector3 value;
				if ((Object)(object)val == (Object)null || Time.unscaledTime > snapUntil[key])
				{
					snapUntil.Remove(key);
					armed.Remove(key);
				}
				else if (val.isLocal && !val.deadSet && armed.TryGetValue(key, out value))
				{
					SnapLocal(val, value);
				}
			}
			if (!IsHost || transitioning)
			{
				return;
			}
			keys.Clear();
			keys.AddRange(this.pending.Keys);
			foreach (string key2 in keys)
			{
				Pending pending = this.pending[key2];
				PlayerAvatar player = pending.Player;
				if ((Object)(object)player == (Object)null || !player.deadSet || pending.Level != CurrentLevel || Time.unscaledTime >= pending.Deadline)
				{
					charges.Refund(key2, pending.Level);
					this.pending.Remove(key2);
					if ((Object)(object)player != (Object)null)
					{
						Dispatch("cancel", player, Vector3.zero);
					}
				}
				else if (!(Time.unscaledTime < pending.Due) && player.isDisabled && !((Object)(object)player.playerDeathHead == (Object)null) && !((Object)(object)TruckSafetySpawnPoint.instance == (Object)null))
				{
					Vector3 position = ((Component)TruckSafetySpawnPoint.instance).transform.position;
					Dispatch("prepare", player, position);
					player.Revive(false);
					charges.Complete(key2);
					this.pending.Remove(key2);
					Log.Info("Return committed in the truck; tier-based health applied; charge spent for this level.");
				}
			}
		}

		private void Dispatch(string phase, PlayerAvatar player, Vector3 position)
		{
			//IL_0003: 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)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_0042: 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_0053: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			Apply(phase, player, position);
			if (SemiFunc.IsMultiplayer())
			{
				NetworkedEvent obj = action;
				Hashtable val = new Hashtable();
				((Dictionary<object, object>)val).Add((object)"phase", (object)phase);
				((Dictionary<object, object>)val).Add((object)"view", (object)player.photonView.ViewID);
				((Dictionary<object, object>)val).Add((object)"point", (object)position);
				obj.RaiseEvent((object)val, NetworkingEvents.RaiseOthers, SendOptions.SendReliable);
			}
		}

		private void Receive(EventData message)
		{
			//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_009e: Unknown result type (might be due to invalid IL or missing references)
			if (PhotonNetwork.MasterClient == null || message.Sender != PhotonNetwork.MasterClient.ActorNumber)
			{
				return;
			}
			object customData = message.CustomData;
			Hashtable val = (Hashtable)((customData is Hashtable) ? customData : null);
			if (val != null && val[(object)"phase"] is string phase && val[(object)"view"] is int num && val[(object)"point"] is Vector3 point)
			{
				PhotonView obj = PhotonView.Find(num);
				PlayerAvatar val2 = ((obj != null) ? ((Component)obj).GetComponent<PlayerAvatar>() : null);
				if ((Object)(object)val2 != (Object)null)
				{
					Apply(phase, val2, point);
				}
			}
		}

		private void Apply(string phase, PlayerAvatar player, Vector3 point)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			string steamID = player.steamID;
			if (phase == "cancel")
			{
				armed.Remove(steamID);
				snapUntil.Remove(steamID);
				return;
			}
			armed[steamID] = point;
			if (!(phase != "prepare") && !((Object)(object)player.playerDeathHead == (Object)null))
			{
				PlayerDeathHead playerDeathHead = player.playerDeathHead;
				playerDeathHead.OverridePositionRotationReset();
				((Component)playerDeathHead).transform.position = point + Vector3.up * 0.25f;
				PhysGrabObject physGrabObject = playerDeathHead.physGrabObject;
				if (IsHost)
				{
					physGrabObject.Teleport(((Component)playerDeathHead).transform.position, Quaternion.identity);
				}
				physGrabObject.centerPoint = ((Component)playerDeathHead).transform.position;
			}
		}

		internal void AfterRevive(PlayerAvatar player)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || player.deadSet || !armed.TryGetValue(player.steamID, out var value))
			{
				return;
			}
			if (player.isLocal)
			{
				SnapLocal(player, value);
				int num = PurchaseRules.RevivalHealth(Plugin.Upgrade.GetLevel(player), player.playerHealth.maxHealth);
				player.playerHealth.health = num;
				StatsManager.instance.SetPlayerHealth(player.steamID, num, false);
				if (SemiFunc.IsMultiplayer())
				{
					((Component)player.playerHealth).GetComponent<PhotonView>().RPC("UpdateHealthRPC", (RpcTarget)1, new object[4]
					{
						num,
						player.playerHealth.maxHealth,
						true,
						false
					});
				}
				ReturnAudio.Play();
				snapUntil[player.steamID] = Time.unscaledTime + 0.6f;
			}
			else
			{
				armed.Remove(player.steamID);
			}
		}

		private static void SnapLocal(PlayerAvatar player, Vector3 point)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: 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)
			((Component)player).transform.position = point;
			player.clientPosition = point;
			player.clientPositionCurrent = point;
			if ((Object)(object)player.playerTransform != (Object)null)
			{
				player.playerTransform.position = point;
			}
			if ((Object)(object)PlayerController.instance != (Object)null)
			{
				((Component)PlayerController.instance).transform.position = point;
				PlayerController.instance.rb.linearVelocity = Vector3.zero;
			}
			player.FallDamageResetSet(1f);
		}

		internal void LevelChanging()
		{
			transitioning = true;
			Reset();
			transitioning = true;
		}

		internal void Reset()
		{
			pending.Clear();
			armed.Clear();
			snapUntil.Clear();
			charges.ClearPending();
			transitioning = false;
		}

		internal void LevelReady()
		{
			transitioning = false;
		}
	}
	internal static class RevivalShop
	{
		internal static Item Item;

		private static GameObject storage;

		private static float nextRegistrationAttempt;

		internal static void Tick()
		{
			if ((Object)(object)Item != (Object)null)
			{
				BindSavedState();
			}
			if ((Object)(object)Item == (Object)null && Time.unscaledTime >= nextRegistrationAttempt)
			{
				nextRegistrationAttempt = Time.unscaledTime + 5f;
				try
				{
					Register();
				}
				catch (Exception ex)
				{
					Log.Error("Return By Death deferred registration failed: " + ex);
				}
			}
		}

		internal static void Register()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Invalid comparison between Unknown and I4
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Expected O, but got Unknown
			if ((Object)(object)Item != (Object)null)
			{
				BindSavedState();
				return;
			}
			Item val = null;
			GameObject val2 = null;
			StatsManager instance = StatsManager.instance;
			if ((Object)(object)instance == (Object)null || instance.itemDictionary.Count == 0)
			{
				return;
			}
			Item[] array = instance.itemDictionary.Values.OrderBy<Item, string>((Item x) => ((Object)x).name, StringComparer.Ordinal).ToArray();
			Item[] array2 = array;
			foreach (Item val3 in array2)
			{
				if ((int)val3.itemType == 3 && val3.prefab != null)
				{
					GameObject prefab = ((PrefabRef<GameObject>)(object)val3.prefab).Prefab;
					if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<ItemUpgradePlayerGrabStrength>() == (Object)null))
					{
						val = val3;
						val2 = prefab;
						break;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				throw new InvalidOperationException("No upgrade prefab with ItemUpgradePlayerGrabStrength found among " + array.Count() + " item assets");
			}
			Log.Info("Return By Death template resolved by component: " + ((Object)val).name + " -> " + ((Object)val2).name);
			storage = new GameObject("ReturnByDeath Prefab Storage");
			storage.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)storage);
			GameObject obj = Object.Instantiate<GameObject>(val2, storage.transform, false);
			((Object)obj).name = "Item Upgrade Return By Death";
			obj.SetActive(true);
			ItemAttributes component = obj.GetComponent<ItemAttributes>();
			ItemUpgradePlayerGrabStrength component2 = obj.GetComponent<ItemUpgradePlayerGrabStrength>();
			if ((Object)(object)component2 != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component2);
			}
			Item val4 = Object.Instantiate<Item>(val);
			((Object)val4).name = "Item Upgrade Return By Death";
			val4.itemName = "RETURN BY DEATH";
			val4.itemNameLocalized = null;
			val4.description = "Return By Death: once per level, return to the truck after death. Three personal tiers: 1 / 50 / 100 HP.";
			val4.value = ScriptableObject.CreateInstance<Value>();
			val4.value.valueMin = 6250f;
			val4.value.valueMax = 7500f;
			val4.maxAmountInShop = 1;
			val4.maxAmount = 6;
			val4.maxPurchase = false;
			component.item = val4;
			obj.AddComponent<RevivalConsume>();
			obj.GetComponent<ItemUpgrade>().upgradeEvent = new UnityEvent();
			CerealVisual.Apply(obj);
			Plugin.Upgrade = Upgrades.GetUpgrade("ReturnByDeath") ?? Upgrades.RegisterUpgrade("ReturnByDeath", val4, (Action<PlayerAvatar, int>)null, (Action<PlayerAvatar, int>)null);
			if (Plugin.Upgrade == null || Items.RegisterItem(component) == null)
			{
				throw new InvalidOperationException("REPOLib rejected Return By Death registration");
			}
			Item = val4;
			BindSavedState();
			Log.Info("Return By Death registered: personal cereal upgrade; one return per level; three health tiers; regular price floor $25,000.");
		}

		private static void BindSavedState()
		{
			StatsManager instance = StatsManager.instance;
			if (!((Object)(object)instance == (Object)null) && Plugin.Upgrade != null)
			{
				string key = "playerUpgradeReturnByDeath";
				if (instance.dictionaryOfDictionaries.TryGetValue(key, out var value))
				{
					Plugin.Upgrade.PlayerDictionary = value;
				}
				else
				{
					instance.dictionaryOfDictionaries[key] = Plugin.Upgrade.PlayerDictionary;
				}
			}
		}
	}
	public sealed class RevivalConsume : MonoBehaviour
	{
		private bool consumed;

		private void Awake()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			ItemUpgrade component = ((Component)this).GetComponent<ItemUpgrade>();
			component.upgradeEvent = new UnityEvent();
			component.upgradeEvent.AddListener(new UnityAction(Consume));
		}

		public void Consume()
		{
			if (consumed)
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(((Component)this).GetComponent<ItemToggle>().playerTogglePhotonID);
			if (!((Object)(object)val == (Object)null) && val.isLocal && Plugin.Upgrade != null)
			{
				consumed = true;
				if (Plugin.Upgrade.GetLevel(val) < 3)
				{
					Plugin.Upgrade.AddLevel(val, 1);
					ReturnAudio.Play();
				}
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
	internal static class RegisterRevivalPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(800)]
		private static void Postfix()
		{
			try
			{
				RevivalShop.Register();
			}
			catch (Exception ex)
			{
				Log.Error("Return By Death registration failed: " + ex);
			}
		}
	}
	[HarmonyPatch(typeof(ShopManager), "GetAllItemsFromStatsManager")]
	internal static class RegisterRevivalBeforeShopPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			try
			{
				RevivalShop.Register();
			}
			catch (Exception ex)
			{
				Log.Error("Return By Death shop registration failed: " + ex);
			}
		}
	}
	[HarmonyPatch(typeof(ShopManager), "UpgradeValueGet")]
	internal static class RevivalPricePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Item item, ref float __result)
		{
			if ((Object)(object)item == (Object)(object)RevivalShop.Item && (Object)(object)item != (Object)null)
			{
				__result = Mathf.Max(25f, __result);
			}
		}
	}
	[HarmonyPatch(typeof(ItemToggle), "ToggleItem")]
	internal static class PreventDuplicatePurchasePatch
	{
		private static bool Prefix(ItemToggle __instance, bool toggle, int player)
		{
			if (!toggle || (Object)(object)((Component)__instance).GetComponent<RevivalConsume>() == (Object)null)
			{
				return true;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(player);
			if ((Object)(object)val == (Object)null || Plugin.Upgrade == null)
			{
				return true;
			}
			if (Plugin.Upgrade.GetLevel(val) < 3)
			{
				return true;
			}
			Log.Info("Return By Death is already owned; cereal box was not consumed.");
			return false;
		}
	}
}
namespace ReturnByDeath.Domain
{
	public static class PurchaseRules
	{
		public const int MaxLevel = 3;

		public static int RevivalHealth(int level, int maxHealth)
		{
			int val = ((level >= 3) ? 100 : ((level != 2) ? 1 : 50));
			return Math.Max(1, Math.Min(val, maxHealth));
		}
	}
	public sealed class ReturnCharge
	{
		public Dictionary<string, int> UsedOnLevel = new Dictionary<string, int>();

		private readonly HashSet<string> reserved = new HashSet<string>();

		public bool TryReserve(string id, int level, bool owned)
		{
			if (!owned || string.IsNullOrEmpty(id) || reserved.Contains(id) || (UsedOnLevel.TryGetValue(id, out var value) && value == level))
			{
				return false;
			}
			reserved.Add(id);
			UsedOnLevel[id] = level;
			return true;
		}

		public void Complete(string id)
		{
			reserved.Remove(id);
		}

		public void Refund(string id, int level)
		{
			if (reserved.Remove(id) && UsedOnLevel.TryGetValue(id, out var value) && value == level)
			{
				UsedOnLevel.Remove(id);
			}
		}

		public void ClearPending()
		{
			reserved.Clear();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}