Decompiled source of CyberSurvivors v0.3.3

plugins/CyberSurvivors/CyberSurvivors.dll

Decompiled a day 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 BepInEx;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CyberSurvivors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.2.0")]
[assembly: AssemblyInformationalVersion("0.3.2")]
[assembly: AssemblyProduct("CyberSurvivors")]
[assembly: AssemblyTitle("CyberSurvivors")]
[assembly: AssemblyVersion("0.3.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CyberSurvivors
{
	[BepInPlugin("local.cybersurvivors", "Cyber Survivors", "0.3.2")]
	[BepInProcess("ULTRAKILL.exe")]
	[DefaultExecutionOrder(10000)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private NativeTheme theme;

		private Vector2 buildScroll;

		private Vector2 summaryScroll;

		public const string Id = "local.cybersurvivors";

		private const string ChoiceState = "local.cybersurvivors.choice";

		internal static Plugin Instance;

		internal readonly RunState Run = new RunState();

		private readonly Random random = new Random();

		private readonly HashSet<int> rewardedDeaths = new HashSet<int>();

		private Harmony harmony;

		private EndlessGrid grid;

		private NewMovement player;

		private GunControl guns;

		private GameObject previousWeapon;

		private GameObject rhythmWeapon;

		private float rhythmUntil;

		private float nextRhythm;

		private bool modalHeld;

		private float savedTimeScale;

		private GameStateManager stateManager;

		private int releaseFrame;

		private float earliestChoice;

		private bool inputArmed;

		private bool healthy;

		private bool showHud = true;

		private readonly EffectFeed feedback = new EffectFeed();

		private readonly HealingBudget healingBudget = new HealingBudget();

		private int waveRestoreDepth;

		private bool meleeHealing;

		private bool banishMode;

		private RunSummary lastSummary;

		private bool showSummary;

		internal bool HoldingChoice
		{
			get
			{
				if (healthy)
				{
					return modalHeld;
				}
				return false;
			}
		}

		private bool Paused
		{
			get
			{
				if (Object.op_Implicit((Object)(object)MonoSingleton<OptionsManager>.Instance))
				{
					return MonoSingleton<OptionsManager>.Instance.paused;
				}
				return false;
			}
		}

		internal bool InRun
		{
			get
			{
				if (healthy && Run.Active && Object.op_Implicit((Object)(object)grid) && ((Behaviour)grid).isActiveAndEnabled && Object.op_Implicit((Object)(object)player) && !player.dead && player.hp > 0 && !player.levelOver)
				{
					return SceneHelper.CurrentScene == "Endless";
				}
				return false;
			}
		}

		private bool CanEditChoice
		{
			get
			{
				if (InRun && !Paused && Application.isFocused)
				{
					return inputArmed;
				}
				return false;
			}
		}

		private void OnGUI()
		{
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: 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)
			//IL_003e: 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_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: 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_0053: 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_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: 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)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			bool inRun = InRun;
			bool flag = healthy && showSummary && lastSummary != null && SceneHelper.CurrentScene == "Endless";
			if (!inRun && !flag)
			{
				return;
			}
			Matrix4x4 matrix = GUI.matrix;
			Color color = GUI.color;
			GUISkin skin = GUI.skin;
			Color backgroundColor = GUI.backgroundColor;
			Color contentColor = GUI.contentColor;
			bool enabled = GUI.enabled;
			int depth = GUI.depth;
			try
			{
				if (theme == null)
				{
					theme = new NativeTheme();
				}
				theme.RefreshFont();
				GUI.skin = theme.Skin;
				GUI.depth = -2000;
				GUI.enabled = true;
				GUI.color = (GUI.backgroundColor = (GUI.contentColor = Color.white));
				float num = Mathf.Min((float)Screen.width / 1280f, (float)Screen.height / 720f);
				float num2 = ((float)Screen.width - 1280f * num) / 2f;
				float num3 = ((float)Screen.height - 720f * num) / 2f;
				GUI.matrix = Matrix4x4.TRS(new Vector3(num2, num3, 0f), Quaternion.identity, Vector3.one * num);
				if (inRun && Run.Choosing && modalHeld && !Paused)
				{
					Fill(new Rect((0f - num2) / num, (0f - num3) / num, (float)Screen.width / num, (float)Screen.height / num), new Color(0f, 0f, 0f, 0.35f));
					DrawChoice();
				}
				else if (flag)
				{
					DrawSummary();
				}
				else
				{
					if (!inRun || !showHud)
					{
						return;
					}
					if (!Paused)
					{
						GUI.BeginGroup(new Rect(0f, 240f, 1280f, 480f));
						try
						{
							DrawCombatHud();
							return;
						}
						finally
						{
							GUI.EndGroup();
						}
					}
					DrawBuildDetails();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Cyber Survivors UI failed; releasing run state: " + ex));
				showSummary = false;
				if (inRun)
				{
					ResetRun("UI error");
				}
			}
			finally
			{
				GUI.matrix = matrix;
				GUI.color = color;
				GUI.skin = skin;
				GUI.backgroundColor = backgroundColor;
				GUI.contentColor = contentColor;
				GUI.enabled = enabled;
				GUI.depth = depth;
			}
		}

		private static void Fill(Rect rect, Color tint)
		{
			//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_000b: Unknown result type (might be due to invalid IL or missing references)
			Color color = GUI.color;
			GUI.color = tint;
			GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
		}

		private void DrawChoice()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0360: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_0401: Unknown result type (might be due to invalid IL or missing references)
			//IL_0459: Unknown result type (might be due to invalid IL or missing references)
			//IL_0496: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f4: Unknown result type (might be due to invalid IL or missing references)
			GUI.Box(new Rect(20f, 16f, 1240f, 688f), GUIContent.none, theme.Panel);
			GUI.Label(new Rect(52f, 30f, 1176f, 48f), Run.ChestOffer ? "-- СУНДУК ЭВОЛЮЦИЙ --" : "-- ВЫБОР УЛУЧШЕНИЯ --", theme.Title);
			GUI.Label(new Rect(52f, 84f, 1176f, 28f), $"CYBER SURVIVORS / ВОЛНА {Run.LastCompletedWave} ЗАВЕРШЕНА / ОСНОВА {Run.BasicCount}/6 / ПРОКЛЯТИЯ {Run.CurseCount}/2", theme.Text);
			GUI.Label(new Rect(52f, 123f, 1176f, 26f), Run.LastChallengeResult, theme.Footer);
			int generation = Run.Generation;
			int offerToken = Run.OfferToken;
			UpgradeId[] array = Run.Choices.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				Upgrade upgrade = Upgrades.Get(array[i]);
				int num = Run.Level(upgrade.Id);
				float num2 = 52 + i * 398;
				Section(caption: upgrade.Cursed ? "[!] ПРОКЛЯТИЕ / БОНУС И ЦЕНА" : (upgrade.Evolution ? "ЭВОЛЮЦИЯ / ПОСТОЯННО" : (upgrade.Tactical ? "ВРЕМЕННЫЙ БОНУС / 1 ВОЛНА" : ((num == 0) ? "НОВЫЙ СЛОТ / УРОВЕНЬ I" : ("УСИЛЕНИЕ / " + Upgrades.LevelName(num) + " → " + Upgrades.LevelName(num + 1))))), rect: new Rect(num2, 158f, 380f, 29f), style: theme.Badge);
				GUI.enabled = inputArmed && Application.isFocused && (!banishMode || !upgrade.Tactical);
				string text = (banishMode ? "ИЗГНАТЬ\n" : "");
				bool num3 = GUI.Button(new Rect(num2, 197f, 380f, 281f), $"<size=23>[{i + 1}] {text}{upgrade.Name.ToUpperInvariant()}</size>\n\n{upgrade.Description(num)}", theme.Card);
				GUI.enabled = true;
				if (num3)
				{
					Choose(i, generation, offerToken);
					return;
				}
			}
			GUI.enabled = inputArmed && Application.isFocused && Run.Rerolls > 0;
			if (GUI.Button(new Rect(52f, 493f, 310f, 44f), $"[R] ПЕРЕБРОС / {Run.Rerolls}", theme.Button))
			{
				Reroll(generation, offerToken);
				return;
			}
			GUI.enabled = inputArmed && Application.isFocused && (Run.Banishes > 0 || banishMode);
			if (GUI.Button(new Rect(380f, 493f, 400f, 44f), banishMode ? "[B] ОТМЕНИТЬ ИЗГНАНИЕ" : ("[B] ИЗГНАНИЕ / " + Run.Banishes), banishMode ? theme.SelectedButton : theme.Button))
			{
				banishMode = !banishMode;
			}
			GUI.enabled = inputArmed && Application.isFocused && Run.Skips > 0;
			if (GUI.Button(new Rect(798f, 493f, 430f, 44f), "ПРОПУСТИТЬ ВЫБОР / " + Run.Skips, theme.Button))
			{
				Skip(generation, offerToken);
				return;
			}
			GUI.enabled = true;
			GUI.Label(new Rect(52f, 549f, 1176f, 42f), banishMode ? "РЕЖИМ ИЗГНАНИЯ: нажмите карточку или 1–3. Полученные уровни сохранятся, но дальнейшее развитие может закрыться." : "[1–3 / МЫШЬ] ВЫБРАТЬ     [ESC] ПАУЗА\nПереброс меняет предложение. Изгнание убирает карточку до конца забега. Пропуск продолжает бой.", theme.Footer);
			Section(new Rect(52f, 595f, 1176f, 25f), "РЕЦЕПТЫ ЭВОЛЮЦИЙ / СУНДУК КАЖДЫЕ 10 ВОЛН", theme.Badge);
			int num4 = 0;
			foreach (Upgrade item in Upgrades.All.Where((Upgrade u) => u.Evolution))
			{
				string text2 = Upgrades.Get(item.Parents[0]).Name + " " + Upgrades.LevelName(Run.Level(item.Parents[0])) + "/III + " + Upgrades.Get(item.Parents[1]).Name + " " + Upgrades.LevelName(Run.Level(item.Parents[1])) + "/I";
				string text3 = (Run.Has(item.Id) ? "ПОЛУЧЕНА" : (Run.IsBanished(item.Id) ? "ИЗГНАНА" : (Run.RecipeReady(item.Id) ? "РЕЦЕПТ ГОТОВ / СУНДУК" : text2)));
				GUI.Label(new Rect(52f, (float)(628 + num4++ * 20), 1176f, 20f), item.Name.ToUpperInvariant() + " / " + text3, theme.Footer);
			}
		}

		private static void Section(Rect rect, string caption, GUIStyle style)
		{
			//IL_0000: 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)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			GUI.Label(rect, caption, style);
			Fill(new Rect(((Rect)(ref rect)).x, ((Rect)(ref rect)).yMax - 2f, ((Rect)(ref rect)).width, 1f), new Color(1f, 1f, 1f, 0.4f));
		}

		private void DrawCombatHud()
		{
			//IL_011a: 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_0187: 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_039d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0452: Unknown result type (might be due to invalid IL or missing references)
			//IL_0486: Unknown result type (might be due to invalid IL or missing references)
			string text = string.Join("  ", from u in Upgrades.All
				where !u.Tactical && Run.Has(u.Id)
				select u.Badge + (u.Evolution ? " [Э]" : (" " + Upgrades.LevelName(Run.Level(u.Id)))));
			if (text.Length == 0)
			{
				text = "ОСНОВА 0/6 / ПРОКЛЯТИЯ 0/2\nСУНДУК КАЖДЫЕ 10 ВОЛН";
			}
			string text2 = $"{Run.Challenge.Name.ToUpperInvariant()}: {Run.Challenge.Progress}/{Run.Challenge.Goal} / ЕЩЁ {Run.Challenge.WavesLeft} ВОЛН\n{Run.Challenge.Description} → +1 переброс";
			float num = TextHeight(text, 450f, 36f);
			float num2 = TextHeight(text2, 450f, 36f);
			float num3 = 55f + num + 8f;
			float num4 = num3 + num2 + 16f;
			GUI.Box(new Rect(18f, 16f, 475f, num4 - 24f), GUIContent.none, theme.HudPanel);
			Section(new Rect(30f, 23f, 450f, 25f), $"CYBER SURVIVORS / ДО ВЫБОРА {Run.WavesUntilChoice} / F6", theme.Hud);
			GUI.Label(new Rect(30f, 55f, 450f, num), text, theme.Detail);
			GUI.Label(new Rect(30f, num3, 450f, num2), text2, theme.Detail);
			string text3 = "";
			if (Run.Has(UpgradeId.Rhythm))
			{
				text3 = ((Time.time < rhythmUntil && Object.op_Implicit((Object)(object)guns) && (Object)(object)guns.currentWeapon == (Object)(object)rhythmWeapon) ? (text3 + "РИТМ: " + (rhythmUntil - Time.time).ToString("0.0") + " с   ") : ((!(Time.time < nextRhythm)) ? (text3 + "РИТМ ГОТОВ   ") : (text3 + "РИТМ / откат: " + (nextRhythm - Time.time).ToString("0.0") + " с   ")));
			}
			if (Run.Has(UpgradeId.OnEdge) && player.hp <= 30)
			{
				text3 += "НА ГРАНИ   ";
			}
			if (Run.Has(UpgradeId.AirAce) && Object.op_Implicit((Object)(object)player.gc) && !player.gc.onGround)
			{
				text3 += "ВОЗДУХ   ";
			}
			if (Run.Tactical.HasValue)
			{
				text3 = text3 + Upgrades.Get(Run.Tactical.Value).Badge + ((Run.Tactical == UpgradeId.DashReserve) ? (" ×" + Run.ReserveCharges) : " / 1 ВОЛНА");
			}
			if (text3.Length > 0)
			{
				float num5 = TextHeight(text3, 450f) + 16f;
				GUI.Box(new Rect(18f, num4, 475f, num5), GUIContent.none, theme.HudPanel);
				GUI.Label(new Rect(30f, num4 + 8f, 450f, num5 - 16f), text3, theme.Detail);
				num4 += num5 + 8f;
			}
			feedback.Trim(Time.time);
			foreach (EffectFeed.Notice item in feedback.Items)
			{
				float num6 = TextHeight(item.Text, 450f) + 12f;
				GUI.Box(new Rect(18f, num4, 475f, num6), GUIContent.none, theme.HudPanel);
				GUI.Label(new Rect(30f, num4 + 6f, 450f, num6 - 12f), item.Text, theme.Detail);
				num4 += num6 + 6f;
			}
		}

		private float TextHeight(string text, float width, float minimum = 20f)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return Mathf.Max(minimum, Mathf.Ceil(theme.Detail.CalcHeight(new GUIContent(text), width)));
		}

		private void DrawBuildDetails()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0417: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			GUI.Box(new Rect(790f, 16f, 470f, 688f), GUIContent.none, theme.Panel);
			Section(new Rect(808f, 30f, 434f, 30f), "-- СБОРКА -- / F6 / F7 ИТОГИ", theme.Hud);
			GUI.Label(new Rect(808f, 73f, 434f, 58f), $"ОСНОВА {Run.BasicCount}/6 / ПРОКЛЯТИЯ {Run.CurseCount}/2\nПеребросы {Run.Rerolls}/5 / изгнания {Run.Banishes} / пропуски {Run.Skips}\nДо выбора {Run.WavesUntilChoice} волн / до сундука {Run.WavesUntilChest}", theme.Detail);
			Upgrade[] array = Upgrades.All.Where((Upgrade u) => !u.Tactical && Run.Has(u.Id)).ToArray();
			float num = array.Sum((Upgrade u) => TextHeight(u.Effects[Run.Level(u.Id) - 1], 399f) + 44f) + 260f;
			buildScroll = GUI.BeginScrollView(new Rect(808f, 145f, 434f, 538f), buildScroll, new Rect(0f, 0f, 405f, num));
			try
			{
				float num2 = 0f;
				Upgrade[] array2 = array;
				foreach (Upgrade upgrade in array2)
				{
					string text = (upgrade.Cursed ? "ПРОКЛЯТИЕ" : (upgrade.Evolution ? "ЭВОЛЮЦИЯ" : (Upgrades.LevelName(Run.Level(upgrade.Id)) + "/III")));
					Section(new Rect(0f, num2, 399f, 25f), upgrade.Name.ToUpperInvariant() + " / " + text, theme.Hud);
					float num4 = TextHeight(upgrade.Effects[Run.Level(upgrade.Id) - 1], 399f) + 44f;
					GUI.Label(new Rect(0f, num2 + 30f, 399f, num4 - 44f), upgrade.Effects[Run.Level(upgrade.Id) - 1], theme.Detail);
					num2 += num4;
				}
				GUI.Label(new Rect(0f, num2, 399f, 40f), $"ИСПЫТАНИЕ: {Run.Challenge.Name.ToUpperInvariant()} {Run.Challenge.Progress}/{Run.Challenge.Goal}", theme.Hud);
				num2 += 42f;
				GUI.Label(new Rect(0f, num2, 399f, 45f), Run.Challenge.Description + ". Осталось волн: " + Run.Challenge.WavesLeft, theme.Detail);
				num2 += 50f;
				Section(new Rect(0f, num2, 399f, 25f), "ИЗГНАНО ДО КОНЦА ЗАБЕГА", theme.Hud);
				num2 += 32f;
				string text2 = string.Join(", ", from u in Upgrades.All
					where Run.IsBanished(u.Id)
					select u.Name);
				GUI.Label(new Rect(0f, num2, 399f, 120f), (text2.Length == 0) ? "Ничего" : text2, theme.Detail);
			}
			finally
			{
				GUI.EndScrollView();
			}
		}

		private void DrawSummary()
		{
			//IL_001b: 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)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: 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_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			RunSummary runSummary = lastSummary;
			GUI.Box(new Rect(790f, 16f, 470f, 688f), GUIContent.none, theme.Panel);
			Section(new Rect(808f, 30f, 434f, 30f), "-- ИТОГИ ЗАБЕГА -- / F7 СКРЫТЬ", theme.Hud);
			string text = TimeSpan.FromSeconds(runSummary.Seconds).ToString("mm\\:ss");
			string text2 = $"Завершено волн: {runSummary.Waves} / боевое время: {text}\nВаших убийств: {runSummary.Kills} / кулаком: {runSummary.MeleeKills} / в воздухе: {runSummary.AirKills}\nУлучшения восстановили: {runSummary.Healing} HP и {runSummary.Stamina:0} выносливости\nВыбрано наград: {runSummary.Choices} / эволюций: {runSummary.Evolutions}\nПеребросов: {runSummary.Rerolls} / изгнаний: {runSummary.Banishes} / пропусков: {runSummary.Skips}\nИспытаний выполнено: {runSummary.Challenges}\nОбычные рекорды Cyber Grind отключены для этого режима.";
			float num = TextHeight(text2, 434f);
			float num2 = 75f + num + 18f;
			GUI.Label(new Rect(808f, 75f, 434f, num), text2, theme.Detail);
			float num3 = 85f + runSummary.Build.Concat(runSummary.Banished).Sum((string item) => TextHeight(item, 399f, 28f) + 6f);
			summaryScroll = GUI.BeginScrollView(new Rect(808f, num2, 434f, 683f - num2), summaryScroll, new Rect(0f, 0f, 405f, num3));
			try
			{
				float num4 = 0f;
				Section(new Rect(0f, num4, 399f, 25f), "СБОРКА ЗАБЕГА", theme.Hud);
				num4 += 34f;
				string[] build = runSummary.Build;
				foreach (string text3 in build)
				{
					float num6 = TextHeight(text3, 399f, 28f);
					GUI.Label(new Rect(0f, num4, 399f, num6), text3, theme.Detail);
					num4 += num6 + 6f;
				}
				Section(new Rect(0f, num4, 399f, 25f), "ИЗГНАНО", theme.Hud);
				num4 += 34f;
				build = runSummary.Banished;
				foreach (string text4 in build)
				{
					float num7 = TextHeight(text4, 399f, 28f);
					GUI.Label(new Rect(0f, num4, 399f, num7), text4, theme.Detail);
					num4 += num7 + 6f;
				}
			}
			finally
			{
				GUI.EndScrollView();
			}
		}

		private void Awake()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			Instance = this;
			try
			{
				harmony = new Harmony("local.cybersurvivors");
				harmony.PatchAll(typeof(Plugin).Assembly);
				SceneManager.sceneUnloaded += SceneUnloaded;
				SceneManager.activeSceneChanged += ActiveSceneChanged;
				healthy = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Cyber Survivors 0.3.2 loaded; translucent native-style UI, six build slots, chests, curses and trials.");
			}
			catch (Exception ex)
			{
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((BaseUnityPlugin)this).Logger.LogError((object)("Cyber Survivors disabled: game API/patch initialization failed. " + ex));
			}
		}

		internal void Begin(EndlessGrid instance)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if (!healthy || SceneHelper.CurrentScene != "Endless")
			{
				return;
			}
			ResetRun("new run");
			grid = instance;
			player = MonoSingleton<NewMovement>.Instance;
			if (Object.op_Implicit((Object)(object)player) && !player.dead && player.hp > 0)
			{
				Run.Begin(random);
				showSummary = false;
				buildScroll = (summaryScroll = Vector2.zero);
				guns = MonoSingleton<GunControl>.Instance;
				if (Object.op_Implicit((Object)(object)guns))
				{
					previousWeapon = guns.currentWeapon;
					guns.OnWeaponChange += WeaponChanged;
				}
				StatsManager instance2 = MonoSingleton<StatsManager>.Instance;
				if (Object.op_Implicit((Object)(object)instance2))
				{
					instance2.majorUsed = true;
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Run started; vanilla scores disabled for this run.");
			}
		}

		internal void WaveStarted(EndlessGrid instance)
		{
			if (InRun && (Object)(object)grid == (Object)(object)instance)
			{
				Run.WaveStarted(instance.currentWave);
			}
		}

		internal void CheckWave(EndlessGrid instance, ActivateNextWave counter, bool testMode)
		{
			if (!InRun || (Object)(object)grid != (Object)(object)instance || testMode || !Object.op_Implicit((Object)(object)counter) || instance.enemyAmount <= 0 || counter.deadEnemies < instance.enemyAmount || !Run.CompleteWave(instance.currentWave, random))
			{
				return;
			}
			try
			{
				HoldChoice();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not open upgrade choice; mod disabled safely for this run: " + ex));
				ResetRun("choice error");
			}
		}

		private void HoldChoice()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: 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_0086: Expected O, but got Unknown
			if (!modalHeld)
			{
				stateManager = GameStateManager.Instance;
				if (!Object.op_Implicit((Object)(object)stateManager))
				{
					throw new InvalidOperationException("GameStateManager is unavailable.");
				}
				savedTimeScale = Time.timeScale;
				modalHeld = true;
				stateManager.RegisterState(new GameState("local.cybersurvivors.choice", ((Component)this).gameObject)
				{
					playerInputLock = (LockMode)1,
					cameraInputLock = (LockMode)1,
					cursorLock = (LockMode)2,
					timerModifier = 0f,
					priority = -100
				});
				inputArmed = false;
				feedback.Clear();
				earliestChoice = Time.unscaledTime + 0.25f;
				FreezeChoice();
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Choice opened after wave {Run.LastCompletedWave} (completed this run: {Run.CompletedCount}).");
			}
		}

		internal void FreezeChoice()
		{
			if (HoldingChoice)
			{
				Time.timeScale = 0f;
			}
		}

		private void ReleaseChoice()
		{
			if (modalHeld)
			{
				modalHeld = false;
				if (Object.op_Implicit((Object)(object)stateManager) && stateManager.IsStateActive("local.cybersurvivors.choice"))
				{
					stateManager.PopState("local.cybersurvivors.choice");
				}
				stateManager = null;
				if (!Paused)
				{
					TimeController instance = MonoSingleton<TimeController>.Instance;
					Time.timeScale = (Object.op_Implicit((Object)(object)instance) ? (instance.timeScale * instance.timeScaleModifier) : ((savedTimeScale > 0f) ? savedTimeScale : 1f));
				}
			}
		}

		internal void ResetRun(string reason)
		{
			if (Run.Active)
			{
				lastSummary = Run.Snapshot(reason);
				showSummary = reason == "game over" || reason.Contains("death");
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Run summary: " + lastSummary.LogLine()));
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Run reset: " + reason));
			}
			Run.Reset();
			ReleaseChoice();
			if (Object.op_Implicit((Object)(object)guns))
			{
				guns.OnWeaponChange -= WeaponChanged;
			}
			rewardedDeaths.Clear();
			grid = null;
			player = null;
			guns = null;
			previousWeapon = (rhythmWeapon = null);
			rhythmUntil = (nextRhythm = 0f);
			releaseFrame = 0;
			inputArmed = false;
			feedback.Clear();
			healingBudget.Reset();
			waveRestoreDepth = 0;
			meleeHealing = (banishMode = false);
		}

		private void Update()
		{
			if (healthy && lastSummary != null && !Run.Choosing && Input.GetKeyDown((KeyCode)288))
			{
				showSummary = !showSummary;
			}
			if (Run.Active && !InRun)
			{
				ResetRun("death, level end or missing run objects");
				return;
			}
			if (InRun && !Run.Choosing && Input.GetKeyDown((KeyCode)287))
			{
				showHud = !showHud;
			}
			if (InRun && !HoldingChoice && !Paused)
			{
				Run.Tick(Time.deltaTime);
			}
			if (!modalHeld)
			{
				return;
			}
			if (!Run.Choosing && Time.frameCount >= releaseFrame)
			{
				ReleaseChoice();
				return;
			}
			FreezeChoice();
			if (Paused || !Application.isFocused)
			{
				inputArmed = false;
				return;
			}
			if (!inputArmed && Time.unscaledTime >= earliestChoice && !ChoiceInputHeld())
			{
				inputArmed = true;
			}
			if (Run.Choosing && inputArmed)
			{
				if (Input.GetKeyDown((KeyCode)114))
				{
					Reroll(Run.Generation, Run.OfferToken);
				}
				else if (Input.GetKeyDown((KeyCode)98) && Run.Banishes > 0)
				{
					banishMode = !banishMode;
				}
				else if (Input.GetKeyDown((KeyCode)49) || Input.GetKeyDown((KeyCode)257))
				{
					Choose(0, Run.Generation, Run.OfferToken);
				}
				else if (Input.GetKeyDown((KeyCode)50) || Input.GetKeyDown((KeyCode)258))
				{
					Choose(1, Run.Generation, Run.OfferToken);
				}
				else if (Input.GetKeyDown((KeyCode)51) || Input.GetKeyDown((KeyCode)259))
				{
					Choose(2, Run.Generation, Run.OfferToken);
				}
			}
		}

		private void LateUpdate()
		{
			FreezeChoice();
		}

		private static bool ChoiceInputHeld()
		{
			if (!Input.GetMouseButton(0) && !Input.GetMouseButton(1) && !Input.GetKey((KeyCode)49) && !Input.GetKey((KeyCode)50) && !Input.GetKey((KeyCode)51) && !Input.GetKey((KeyCode)114) && !Input.GetKey((KeyCode)98) && !Input.GetKey((KeyCode)257) && !Input.GetKey((KeyCode)258))
			{
				return Input.GetKey((KeyCode)259);
			}
			return true;
		}

		private void Choose(int index, int generation, int offerToken)
		{
			if (!InRun || Paused || !Application.isFocused || !inputArmed)
			{
				return;
			}
			if (banishMode)
			{
				Banish(index, generation, offerToken);
				return;
			}
			Upgrade[] source = Upgrades.All.Where((Upgrade u) => u.Evolution && !Run.RecipeReady(u.Id) && !Run.Has(u.Id)).ToArray();
			if (!Run.TryChoose(index, generation, offerToken, out var selected))
			{
				return;
			}
			inputArmed = false;
			releaseFrame = Time.frameCount + 2;
			Upgrade upgrade = Upgrades.Get(selected);
			string text = (upgrade.Tactical ? "НА СЛЕДУЮЩУЮ ВОЛНУ" : Upgrades.LevelName(Run.Level(selected)));
			Notify("selected", upgrade.Name.ToUpperInvariant() + " / " + text, 4f);
			foreach (Upgrade item in source.Where((Upgrade u) => Run.RecipeReady(u.Id)))
			{
				Notify("unlock_" + item.Id, "РЕЦЕПТ ГОТОВ / СУНДУК: " + item.Name, 5f);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Selected: " + selected.ToString() + " / " + text));
		}

		private void RearmChoice()
		{
			inputArmed = (banishMode = false);
			earliestChoice = Time.unscaledTime + 0.15f;
		}

		private void Reroll(int generation, int token)
		{
			if (CanEditChoice && Run.TryReroll(generation, token, random))
			{
				RearmChoice();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Offer rerolled; remaining=" + Run.Rerolls));
			}
		}

		private void Banish(int index, int generation, int token)
		{
			if (CanEditChoice && Run.TryBanish(index, generation, token, random))
			{
				RearmChoice();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Card banished; remaining=" + Run.Banishes));
			}
		}

		private void Skip(int generation, int token)
		{
			if (CanEditChoice && Run.TrySkip(generation, token))
			{
				inputArmed = (banishMode = false);
				releaseFrame = Time.frameCount + 2;
				Notify("skip", "ВЫБОР ПРОПУЩЕН");
			}
		}

		internal bool MaskInput(InputActionState action)
		{
			if (!HoldingChoice)
			{
				return false;
			}
			InputManager instance = MonoSingleton<InputManager>.Instance;
			if (Object.op_Implicit((Object)(object)instance))
			{
				return action != instance.InputSource.Pause;
			}
			return false;
		}

		private void WeaponChanged(GameObject weapon)
		{
			if (!((Object)(object)weapon == (Object)(object)previousWeapon))
			{
				previousWeapon = weapon;
				if (InRun && !HoldingChoice && Object.op_Implicit((Object)(object)weapon) && Run.Has(UpgradeId.Rhythm) && !(Time.time < nextRhythm))
				{
					rhythmWeapon = weapon;
					rhythmUntil = Time.time + UpgradeMath.RhythmDuration(Run.Level(UpgradeId.Rhythm));
					nextRhythm = Time.time + 5f;
					Notify("rhythm", "СМЕНА РИТМА / +" + Mathf.RoundToInt(UpgradeMath.RhythmBonus(Run.Level(UpgradeId.Rhythm)) * 100f) + "%");
				}
			}
		}

		private bool PlayerWeapon(GameObject weapon)
		{
			if (!Object.op_Implicit((Object)(object)weapon) || !Object.op_Implicit((Object)(object)guns))
			{
				return false;
			}
			if (!weapon.transform.IsChildOf(((Component)player).transform))
			{
				return guns.allWeapons.Contains(weapon);
			}
			return true;
		}

		internal DamageSnapshot BeforeDamage(EnemyIdentifier enemy, GameObject weapon, bool explosion, ref float damage)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			if (!InRun || HoldingChoice || !Object.op_Implicit((Object)(object)enemy) || enemy.dead || damage <= 0f)
			{
				return default(DamageSnapshot);
			}
			bool flag = !explosion && (enemy.hitter == "punch" || enemy.hitter == "heavypunch");
			bool flag2 = PlayerWeapon(weapon);
			if (!flag && !flag2)
			{
				return default(DamageSnapshot);
			}
			bool flag3 = Object.op_Implicit((Object)(object)player.gc) && !player.gc.onGround;
			bool flag4 = player.hp <= 30;
			Vector3 val = ((Component)enemy).transform.position - ((Component)player).transform.position;
			bool flag5 = ((Vector3)(ref val)).sqrMagnitude <= 64f;
			float num = damage;
			RunState run = Run;
			bool rhythm = Run.Has(UpgradeId.Rhythm) && flag2 && (Object)(object)weapon == (Object)(object)rhythmWeapon && Time.time < rhythmUntil;
			val = ((Component)enemy).transform.position - ((Component)player).transform.position;
			damage = num * UpgradeMath.DamageFactor(run, rhythm, flag3, explosion, flag4, flag5, ((Vector3)(ref val)).sqrMagnitude > 400f);
			return new DamageSnapshot
			{
				Eligible = true,
				Melee = flag,
				Generation = Run.Generation,
				Angel = (flag3 && flag4 && Run.Has(UpgradeId.FallenAngel)),
				Epicenter = (explosion && flag5 && Run.Has(UpgradeId.Epicenter)),
				HealthBefore = enemy.health,
				Airborne = flag3
			};
		}

		internal void AfterDamage(EnemyIdentifier enemy, DamageSnapshot snapshot)
		{
			if (!snapshot.Eligible || snapshot.Generation != Run.Generation || !InRun || !Object.op_Implicit((Object)(object)enemy))
			{
				return;
			}
			if (snapshot.Epicenter && (enemy.dead || enemy.health < snapshot.HealthBefore))
			{
				Notify("epicenter", "ЭПИЦЕНТР / +35%", 2.5f, 0.8f);
			}
			if (!enemy.dead || !rewardedDeaths.Add(((Object)enemy).GetInstanceID()))
			{
				return;
			}
			Run.RecordKill(snapshot.Melee, snapshot.Airborne);
			if (snapshot.Melee)
			{
				Heal(UpgradeMath.BloodHeal(Run.Level(UpgradeId.Bloodlust)), "blood", "КРОВАВЫЙ АЗАРТ");
				if (Run.Has(UpgradeId.BloodEngine))
				{
					RestoreStamina(30f, "engine", "КРОВАВЫЙ ДВИГАТЕЛЬ");
				}
			}
			if (snapshot.Angel)
			{
				Heal(8, "angel", "ПАДШИЙ АНГЕЛ");
			}
			if (random.NextDouble() < (double)UpgradeMath.DashChance(Run.Level(UpgradeId.SpareDash)))
			{
				RestoreStamina(100f, "dash", "РЫВОК ВОССТАНОВЛЕН");
			}
			if (player.boostCharge < 300f && Run.ConsumeReserve())
			{
				RestoreStamina(100f, "reserve", "РЕЗЕРВ РЫВКА");
			}
		}

		private void Heal(int amount, string key, string label)
		{
			if (amount > 0)
			{
				int hp = player.hp;
				bool flag = meleeHealing;
				meleeHealing = key == "blood";
				try
				{
					player.GetHealth(amount, true, false, true);
				}
				finally
				{
					meleeHealing = flag;
				}
				int num = player.hp - hp;
				Run.RecordHealing(num);
				if (num > 0)
				{
					Notify(key, label + " / +" + num + " HP");
				}
			}
		}

		private void RestoreStamina(float amount, string key, string label)
		{
			if (Run.Has(UpgradeId.Overdrive))
			{
				amount *= 0.5f;
			}
			float boostCharge = player.boostCharge;
			player.boostCharge = UpgradeMath.RestoreDash(boostCharge, amount);
			int num = Mathf.RoundToInt(player.boostCharge - boostCharge);
			Run.RecordStamina(player.boostCharge - boostCharge);
			if (num > 0)
			{
				Notify(key, label + " / +" + num);
			}
		}

		private void Notify(string key, string text, float duration = 2.5f, float cooldown = 0f)
		{
			feedback.Push(key, text, Time.time, duration, cooldown);
		}

		internal void EnemyEnabled(EnemyIdentifier enemy)
		{
			if (Run.Active && Object.op_Implicit((Object)(object)enemy))
			{
				rewardedDeaths.Remove(((Object)enemy).GetInstanceID());
			}
		}

		internal void BeforeHurt(NewMovement instance, ref int damage, bool instablack, bool ignoreInvincibility)
		{
			if (InRun && (Object)(object)instance == (Object)(object)player)
			{
				damage = UpgradeMath.ArmourDamage(damage, instablack || ignoreInvincibility, Run.Level(UpgradeId.Armour), Run.TacticalActive(UpgradeId.FieldShield), Run.Has(UpgradeId.GlassCannon));
			}
		}

		internal void AfterHurt(NewMovement instance, int hpBefore)
		{
			if (InRun && (Object)(object)instance == (Object)(object)player)
			{
				Run.RecordHurt(hpBefore - player.hp);
			}
		}

		internal bool BeginWaveRestore()
		{
			if (!InRun)
			{
				return false;
			}
			waveRestoreDepth++;
			return true;
		}

		internal void EndWaveRestore(bool ownsScope)
		{
			if (ownsScope && waveRestoreDepth > 0)
			{
				waveRestoreDepth--;
			}
		}

		internal bool ModifyHealing(NewMovement instance, ref int amount, bool fromExplosion)
		{
			if (!InRun || (Object)(object)instance != (Object)(object)player || amount <= 0 || (player.exploded && fromExplosion))
			{
				return true;
			}
			amount = healingBudget.Apply(amount, Run.Has(UpgradeId.Thirst), meleeHealing, waveRestoreDepth > 0);
			return amount > 0;
		}

		private void SceneUnloaded(Scene scene)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)grid) && ((Component)grid).gameObject.scene == scene)
			{
				ResetRun("scene unloaded");
			}
		}

		private void ActiveSceneChanged(Scene previous, Scene next)
		{
			ResetRun("scene changed");
		}

		private void OnDisable()
		{
			ResetRun("plugin disabled");
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			healthy = false;
			theme?.Dispose();
			theme = null;
			SceneManager.sceneUnloaded -= SceneUnloaded;
			SceneManager.activeSceneChanged -= ActiveSceneChanged;
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	public sealed class EffectFeed
	{
		public sealed class Notice
		{
			public string Key;

			public string Text;

			public float ExpiresAt;

			public float NextUpdate;
		}

		private readonly List<Notice> items = new List<Notice>();

		public IReadOnlyList<Notice> Items => items;

		public void Clear()
		{
			items.Clear();
		}

		public void Trim(float now)
		{
			items.RemoveAll((Notice item) => item.ExpiresAt <= now);
		}

		public void Push(string key, string text, float now, float duration = 2.5f, float cooldown = 0f)
		{
			Trim(now);
			Notice notice = items.Find((Notice n) => n.Key == key);
			if (notice != null && now < notice.NextUpdate)
			{
				return;
			}
			if (notice == null)
			{
				if (items.Count == 4)
				{
					items.RemoveAt(0);
				}
				notice = new Notice
				{
					Key = key
				};
				items.Add(notice);
			}
			notice.Text = text;
			notice.ExpiresAt = now + duration;
			notice.NextUpdate = now + cooldown;
		}
	}
	internal sealed class NativeTheme : IDisposable
	{
		internal readonly GUISkin Skin;

		internal readonly GUIStyle Title;

		internal readonly GUIStyle Text;

		internal readonly GUIStyle Card;

		internal readonly GUIStyle Footer;

		internal readonly GUIStyle Badge;

		internal readonly GUIStyle Hud;

		internal readonly GUIStyle Detail;

		internal readonly GUIStyle Button;

		internal readonly GUIStyle SelectedButton;

		internal readonly GUIStyle Panel;

		internal readonly GUIStyle HudPanel;

		private readonly List<Object> owned = new List<Object>();

		private Font font;

		private bool nativeFont;

		private float nextFontSearch;

		internal NativeTheme()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: 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_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0255: Expected O, but got Unknown
			//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Expected O, but got Unknown
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e2: Expected O, but got Unknown
			//IL_033b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0345: Expected O, but got Unknown
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Expected O, but got Unknown
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Expected O, but got Unknown
			Skin = Object.Instantiate<GUISkin>(GUI.skin);
			owned.Add((Object)(object)Skin);
			Texture2D val = Frame(new Color(1f, 1f, 1f, 0.7f), new Color(0f, 0f, 0f, 0.5f));
			Texture2D val2 = Frame(Color.white, Color.white);
			Texture2D val3 = Frame(new Color(1f, 1f, 1f, 0.45f), new Color(0f, 0f, 0f, 0.32f));
			Texture2D val4 = Frame(new Color(1f, 1f, 1f, 0.35f), new Color(0f, 0f, 0f, 0.18f), 1);
			Texture2D val5 = Frame(Color.white, Color.black, 1);
			Title = Label(32, (TextAnchor)4);
			Text = Label(18, (TextAnchor)4);
			Footer = Label(15, (TextAnchor)0, (Color?)new Color(0.72f, 0.72f, 0.72f));
			Badge = Label(15, (TextAnchor)3);
			Hud = Label(16, (TextAnchor)3);
			Detail = Label(14, (TextAnchor)0);
			Button = Framed(val, val2, 17);
			SelectedButton = Framed(val2, val, 17);
			SelectedButton.normal.textColor = Color.black;
			SelectedButton.hover.textColor = Color.white;
			SelectedButton.active.textColor = Color.white;
			SelectedButton.focused.textColor = Color.black;
			Card = Framed(val, val2, 19);
			Card.richText = true;
			Card.alignment = (TextAnchor)0;
			Card.padding = new RectOffset(20, 20, 25, 20);
			Panel = Framed(val3, val3, 0);
			HudPanel = Framed(val4, val4, 0);
			Skin.verticalScrollbar = Framed(val5, val5, 0);
			Skin.verticalScrollbar.name = "verticalScrollbar";
			Skin.verticalScrollbar.fixedWidth = 14f;
			Skin.verticalScrollbar.padding = new RectOffset(3, 3, 3, 3);
			Skin.verticalScrollbar.margin = new RectOffset(6, 0, 0, 0);
			Skin.verticalScrollbarThumb = Framed(val2, val2, 0);
			Skin.verticalScrollbarThumb.fixedWidth = 8f;
			Skin.verticalScrollbarThumb.fixedHeight = 0f;
			Skin.verticalScrollbarThumb.stretchHeight = true;
			Skin.verticalScrollbarThumb.padding = new RectOffset();
			Skin.verticalScrollbarThumb.border = new RectOffset();
			Skin.verticalScrollbarThumb.normal.background = (Skin.verticalScrollbarThumb.hover.background = (Skin.verticalScrollbarThumb.active.background = Texture2D.whiteTexture));
			Skin.verticalScrollbarThumb.overflow = new RectOffset();
			Skin.verticalScrollbarUpButton = Empty();
			Skin.verticalScrollbarDownButton = Empty();
			Skin.horizontalScrollbar = Empty();
			Skin.horizontalScrollbarThumb = Empty();
			Skin.horizontalScrollbarLeftButton = Empty();
			Skin.horizontalScrollbarRightButton = Empty();
			Skin.scrollView = Empty();
			RefreshFont();
		}

		private static GUIStyle Empty()
		{
			//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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			return new GUIStyle
			{
				fixedWidth = 0f,
				fixedHeight = 0f
			};
		}

		private static GUIStyle Label(int size, TextAnchor alignment, Color? tint = null)
		{
			//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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = size,
				fontStyle = (FontStyle)0,
				alignment = alignment,
				wordWrap = true,
				richText = false,
				padding = new RectOffset(),
				margin = new RectOffset()
			};
			val.normal.textColor = (Color)(((??)tint) ?? Color.white);
			return val;
		}

		private static GUIStyle Framed(Texture2D normal, Texture2D hover, int size)
		{
			//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_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: 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_0045: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0051: Expected O, but got Unknown
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: 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_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: 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_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			GUIStyle val = new GUIStyle
			{
				fontSize = size,
				fontStyle = (FontStyle)0,
				wordWrap = true,
				alignment = (TextAnchor)4,
				border = new RectOffset(12, 12, 12, 12),
				padding = new RectOffset(12, 12, 8, 8),
				margin = new RectOffset()
			};
			GUIStyleState normal2 = val.normal;
			Texture2D background = (val.onNormal.background = normal);
			normal2.background = background;
			GUIStyleState hover2 = val.hover;
			background = (val.onHover.background = hover);
			hover2.background = background;
			GUIStyleState active = val.active;
			background = (val.onActive.background = hover);
			active.background = background;
			GUIStyleState focused = val.focused;
			background = (val.onFocused.background = normal);
			focused.background = background;
			GUIStyleState normal3 = val.normal;
			Color textColor = (val.onNormal.textColor = Color.white);
			normal3.textColor = textColor;
			GUIStyleState hover3 = val.hover;
			textColor = (val.onHover.textColor = Color.black);
			hover3.textColor = textColor;
			GUIStyleState active2 = val.active;
			textColor = (val.onActive.textColor = Color.black);
			active2.textColor = textColor;
			GUIStyleState focused2 = val.focused;
			textColor = (val.onFocused.textColor = Color.white);
			focused2.textColor = textColor;
			return val;
		}

		private Texture2D Frame(Color edge, Color fill, int thickness = 2)
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			//IL_007b: 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_007c: Unknown result type (might be due to invalid IL or missing references)
			Color[] array = (Color[])(object)new Color[1600];
			for (int i = 0; i < 40; i++)
			{
				for (int j = 0; j < 40; j++)
				{
					float num = (float)j + 0.5f;
					float num2 = (float)i + 0.5f;
					if (Inside(num, num2, 40f, 10f))
					{
						float cut = 10f - (float)thickness * (2f - Mathf.Sqrt(2f));
						array[i * 40 + j] = (Inside(num - (float)thickness, num2 - (float)thickness, 40 - thickness * 2, cut) ? fill : edge);
					}
				}
			}
			Texture2D val = new Texture2D(40, 40, (TextureFormat)4, false)
			{
				name = "CyberSurvivorsFrame",
				filterMode = (FilterMode)0,
				wrapMode = (TextureWrapMode)1
			};
			owned.Add((Object)(object)val);
			val.SetPixels(array);
			val.Apply(false, true);
			return val;
		}

		private static bool Inside(float x, float y, float size, float cut)
		{
			if (x >= 0f && y >= 0f && x <= size && y <= size && x + y >= cut && x + y <= size * 2f - cut && x - y <= size - cut)
			{
				return y - x <= size - cut;
			}
			return false;
		}

		internal void RefreshFont()
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			if ((Object.op_Implicit((Object)(object)font) && nativeFont) || Time.unscaledTime < nextFontSearch)
			{
				return;
			}
			nextFontSearch = Time.unscaledTime + 5f;
			Font val = null;
			try
			{
				Font[] array = Resources.FindObjectsOfTypeAll<Font>();
				foreach (Font val2 in array)
				{
					if (Object.op_Implicit((Object)(object)val2) && IsNative(val2) && val2.HasCharacter('Ж') && val2.HasCharacter('я'))
					{
						val = val2;
						break;
					}
				}
				if (!Object.op_Implicit((Object)(object)val))
				{
					string text = Path.Combine(Paths.ConfigPath, "ultrakull", "fonts", "ru-RU", "VCR_OSD_MONO.ttf");
					if (File.Exists(text))
					{
						Font val3 = new Font(text);
						if (val3.HasCharacter('Ж') && val3.HasCharacter('я'))
						{
							val = val3;
							owned.Add((Object)(object)val3);
						}
						else
						{
							Object.Destroy((Object)(object)val3);
						}
					}
				}
			}
			catch (Exception)
			{
			}
			nativeFont = Object.op_Implicit((Object)(object)val);
			if (!Object.op_Implicit((Object)(object)val))
			{
				val = (Object.op_Implicit((Object)(object)font) ? font : (Object.op_Implicit((Object)(object)Skin.font) ? Skin.font : GUI.skin.label.font));
			}
			font = val;
			Skin.font = font;
			GUIStyle[] array2 = (GUIStyle[])(object)new GUIStyle[9] { Title, Text, Card, Footer, Badge, Hud, Detail, Button, SelectedButton };
			for (int i = 0; i < array2.Length; i++)
			{
				array2[i].font = font;
			}
		}

		private static bool IsNative(Font value)
		{
			return ((Object)value).name.IndexOf("vcr", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		public void Dispose()
		{
			foreach (Object item in owned)
			{
				if (Object.op_Implicit(item))
				{
					Object.Destroy(item);
				}
			}
			owned.Clear();
		}
	}
	[HarmonyPatch(typeof(EndlessGrid), "OnTriggerEnter")]
	internal static class BeginPatch
	{
		private static void Prefix(EndlessGrid __instance, Collider other)
		{
			Collider component = ((Component)__instance).GetComponent<Collider>();
			if (Object.op_Implicit((Object)(object)other) && ((Component)other).CompareTag("Player") && Object.op_Implicit((Object)(object)component) && component.enabled)
			{
				Plugin.Instance?.Begin(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(EndlessGrid), "NextWave")]
	internal static class WaveStartedPatch
	{
		private static void Prefix(out bool __state)
		{
			__state = Object.op_Implicit((Object)(object)Plugin.Instance) && Plugin.Instance.BeginWaveRestore();
		}

		private static void Postfix(EndlessGrid __instance)
		{
			Plugin.Instance?.WaveStarted(__instance);
		}

		private static Exception Finalizer(Exception __exception, bool __state)
		{
			Plugin.Instance?.EndWaveRestore(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(EndlessGrid), "Update")]
	internal static class WaveCompletePatch
	{
		private static void Prefix(EndlessGrid __instance, ActivateNextWave ___anw, bool ___testMode)
		{
			Plugin.Instance?.CheckWave(__instance, ___anw, ___testMode);
		}
	}
	[HarmonyPatch(typeof(EnemyIdentifier), "DeliverDamage")]
	internal static class DamagePatch
	{
		private static void Prefix(EnemyIdentifier __instance, GameObject sourceWeapon, bool fromExplosion, ref float multiplier, out DamageSnapshot __state)
		{
			__state = (Object.op_Implicit((Object)(object)Plugin.Instance) ? Plugin.Instance.BeforeDamage(__instance, sourceWeapon, fromExplosion, ref multiplier) : default(DamageSnapshot));
		}

		private static void Postfix(EnemyIdentifier __instance, DamageSnapshot __state)
		{
			Plugin.Instance?.AfterDamage(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(EnemyIdentifier), "OnEnable")]
	internal static class EnemyEnabledPatch
	{
		private static void Postfix(EnemyIdentifier __instance)
		{
			Plugin.Instance?.EnemyEnabled(__instance);
		}
	}
	[HarmonyPatch(typeof(NewMovement), "GetHurt")]
	internal static class HurtPatch
	{
		private static void Prefix(NewMovement __instance, ref int damage, bool instablack, bool ignoreInvincibility, out int __state)
		{
			__state = __instance.hp;
			Plugin.Instance?.BeforeHurt(__instance, ref damage, instablack, ignoreInvincibility);
		}

		private static void Postfix(NewMovement __instance, int __state)
		{
			Plugin.Instance?.AfterHurt(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(NewMovement), "GetHealth")]
	internal static class HealingPatch
	{
		private static bool Prefix(NewMovement __instance, ref int health, bool fromExplosion)
		{
			if (Object.op_Implicit((Object)(object)Plugin.Instance))
			{
				return Plugin.Instance.ModifyHealing(__instance, ref health, fromExplosion);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(FinalCyberRank), "GameOver")]
	internal static class GameOverPatch
	{
		private static void Prefix()
		{
			Plugin.Instance?.ResetRun("game over");
		}
	}
	[HarmonyPatch(typeof(NewMovement), "Respawn")]
	internal static class RespawnPatch
	{
		private static void Prefix()
		{
			Plugin.Instance?.ResetRun("respawn");
		}
	}
	[HarmonyPatch(typeof(StatsManager), "Restart")]
	internal static class RestartPatch
	{
		private static void Prefix()
		{
			Plugin.Instance?.ResetRun("restart requested");
		}
	}
	[HarmonyPatch]
	internal static class InputPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.PropertyGetter(typeof(InputActionState), "IsPressed");
			yield return AccessTools.PropertyGetter(typeof(InputActionState), "WasPerformedThisFrame");
			yield return AccessTools.PropertyGetter(typeof(InputActionState), "WasCanceledThisFrame");
		}

		private static void Postfix(InputActionState __instance, ref bool __result)
		{
			if (Object.op_Implicit((Object)(object)Plugin.Instance) && Plugin.Instance.MaskInput(__instance))
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch]
	internal static class TimePatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			string[] array = new string[4] { "ContinueTime", "RestoreTime", "InitializeValues", "Update" };
			foreach (string text in array)
			{
				yield return AccessTools.Method(typeof(TimeController), text, (Type[])null, (Type[])null);
			}
		}

		private static void Postfix()
		{
			Plugin.Instance?.FreezeChoice();
		}
	}
	internal struct DamageSnapshot
	{
		public bool Eligible;

		public bool Melee;

		public int Generation;

		public bool Angel;

		public bool Epicenter;

		public float HealthBefore;

		public bool Airborne;
	}
	public enum ChallengeKind
	{
		Melee,
		Airborne,
		Untouched
	}
	public sealed class RunChallenge
	{
		public ChallengeKind Kind { get; private set; }

		public int Progress { get; private set; }

		public int Goal
		{
			get
			{
				if (Kind != ChallengeKind.Melee)
				{
					if (Kind != ChallengeKind.Airborne)
					{
						return 1;
					}
					return 10;
				}
				return 8;
			}
		}

		public int WavesLeft { get; private set; }

		public string Name
		{
			get
			{
				if (Kind != ChallengeKind.Melee)
				{
					if (Kind != ChallengeKind.Airborne)
					{
						return "Чистая волна";
					}
					return "Воздушная охота";
				}
				return "Кулачный бой";
			}
		}

		public string Description
		{
			get
			{
				if (Kind != ChallengeKind.Melee)
				{
					if (Kind != ChallengeKind.Airborne)
					{
						return "Завершить одну волну без потери HP";
					}
					return "10 ваших убийств в воздухе";
				}
				return "8 прямых добиваний кулаком";
			}
		}

		public void Start(ChallengeKind kind)
		{
			Kind = kind;
			Progress = 0;
			WavesLeft = 5;
		}

		public void Kill(bool melee, bool airborne)
		{
			if (WavesLeft > 0 && ((Kind == ChallengeKind.Melee && melee) || (Kind == ChallengeKind.Airborne && airborne)))
			{
				Progress = Math.Min(Goal, Progress + 1);
			}
		}

		public bool FinishWave(bool hurt)
		{
			if (WavesLeft <= 0)
			{
				return false;
			}
			if (Kind == ChallengeKind.Untouched && !hurt)
			{
				Progress = 1;
			}
			WavesLeft--;
			if (WavesLeft == 0)
			{
				return Progress >= Goal;
			}
			return false;
		}

		public void Reset()
		{
			Progress = 0;
			WavesLeft = 0;
		}
	}
	public sealed class RunLedger
	{
		public int Kills;

		public int MeleeKills;

		public int AirKills;

		public int Healing;

		public int Choices;

		public int RerollsUsed;

		public int BanishesUsed;

		public int SkipsUsed;

		public int ChallengesWon;

		public float Stamina;

		public double Seconds;
	}
	public sealed class RunSummary
	{
		public string Reason { get; }

		public int Waves { get; }

		public int Kills { get; }

		public int MeleeKills { get; }

		public int AirKills { get; }

		public int Healing { get; }

		public int Choices { get; }

		public int Rerolls { get; }

		public int Banishes { get; }

		public int Skips { get; }

		public int Challenges { get; }

		public int Evolutions { get; }

		public float Stamina { get; }

		public double Seconds { get; }

		public string[] Build { get; }

		public string[] Banished { get; }

		public RunSummary(RunState run, string reason)
		{
			RunLedger stats = run.Stats;
			Reason = reason;
			Waves = run.CompletedCount;
			Kills = stats.Kills;
			MeleeKills = stats.MeleeKills;
			AirKills = stats.AirKills;
			Healing = stats.Healing;
			Stamina = stats.Stamina;
			Seconds = stats.Seconds;
			Choices = stats.Choices;
			Rerolls = stats.RerollsUsed;
			Banishes = stats.BanishesUsed;
			Skips = stats.SkipsUsed;
			Challenges = stats.ChallengesWon;
			Evolutions = run.EvolutionCount;
			Build = (from u in Upgrades.All
				where !u.Tactical && run.Has(u.Id)
				select u.Name + " " + Upgrades.LevelName(run.Level(u.Id))).ToArray();
			Banished = (from u in Upgrades.All
				where run.IsBanished(u.Id)
				select u.Name).ToArray();
		}

		public string LogLine()
		{
			return string.Format("waves={0}; kills={1}; melee={2}; air={3}; heal={4}; stamina={5:0}; choices={6}; rerolls={7}; banishes={8}; skips={9}; challenges={10}; build=[{11}]", Waves, Kills, MeleeKills, AirKills, Healing, Stamina, Choices, Rerolls, Banishes, Skips, Challenges, string.Join(", ", Build));
		}
	}
	public sealed class HealingBudget
	{
		private int normalRemainder;

		private int meleeRemainder;

		public int Apply(int amount, bool thirsty, bool melee, bool waveRestore)
		{
			if (!thirsty || waveRestore || amount <= 0)
			{
				return amount;
			}
			return Scale(amount, melee);
		}

		public int Scale(int amount, bool melee)
		{
			if (amount <= 0)
			{
				return amount;
			}
			int num = (melee ? meleeRemainder : normalRemainder);
			long num2 = (long)amount * (long)(melee ? 150 : 75) + num;
			int result = (int)Math.Min(2147483647L, num2 / 100);
			if (melee)
			{
				meleeRemainder = (int)(num2 % 100);
				return result;
			}
			normalRemainder = (int)(num2 % 100);
			return result;
		}

		public void Reset()
		{
			normalRemainder = (meleeRemainder = 0);
		}
	}
	public enum UpgradeId
	{
		Bloodlust,
		Rhythm,
		AirAce,
		BlastCharge,
		SpareDash,
		OnEdge,
		CloseCombat,
		Armour,
		BloodEngine,
		FallenAngel,
		Epicenter,
		Assault,
		FieldShield,
		DashReserve,
		GlassCannon,
		Thirst,
		Overdrive,
		LongVow
	}
	public sealed class Upgrade
	{
		public readonly UpgradeId Id;

		public readonly string Name;

		public readonly string Badge;

		public readonly string[] Effects;

		public readonly UpgradeId[] Parents;

		public readonly bool Tactical;

		public readonly bool Cursed;

		public bool Evolution => Parents.Length != 0;

		public int MaxLevel
		{
			get
			{
				if (!Tactical)
				{
					return Effects.Length;
				}
				return 0;
			}
		}

		public Upgrade(UpgradeId id, string name, string badge, string[] effects, UpgradeId[] parents = null, bool tactical = false, bool cursed = false)
		{
			Id = id;
			Name = name;
			Badge = badge;
			Effects = effects;
			Parents = parents ?? Array.Empty<UpgradeId>();
			Tactical = tactical;
			Cursed = cursed;
		}

		public string Description(int currentLevel)
		{
			if (Tactical)
			{
				return Effects[0];
			}
			int num = Math.Min(currentLevel, Effects.Length - 1);
			if (currentLevel <= 0 || currentLevel >= MaxLevel)
			{
				return Effects[num];
			}
			return "Сейчас: " + Effects[currentLevel - 1] + "\n\nПосле выбора: " + Effects[num];
		}
	}
	public static class Upgrades
	{
		public static readonly Upgrade[] All = new Upgrade[18]
		{
			new Upgrade(UpgradeId.Bloodlust, "Кровавый азарт", "КРОВЬ", new string[3] { "Прямое добивание кулаком: +5 HP.", "Прямое добивание кулаком: +8 HP.", "Прямое добивание кулаком: +12 HP." }),
			new Upgrade(UpgradeId.Rhythm, "Смена ритма", "РИТМ", new string[3] { "+25% урона оружием на 2 с после его смены. Откат 5 с.", "+35% урона оружием на 2,5 с после его смены. Откат 5 с.", "+45% урона оружием на 3 с после его смены. Откат 5 с." }),
			new Upgrade(UpgradeId.AirAce, "Воздушный ас", "ВОЗДУХ", new string[3] { "+20% урона, пока ВЫ в воздухе.", "+30% урона, пока ВЫ в воздухе.", "+40% урона, пока ВЫ в воздухе." }),
			new Upgrade(UpgradeId.BlastCharge, "Мощный взрыв", "ВЗРЫВ", new string[3] { "+25% урона ваших взрывов по врагам.", "+35% урона ваших взрывов по врагам.", "+50% урона ваших взрывов по врагам." }),
			new Upgrade(UpgradeId.SpareDash, "Запасной рывок", "РЫВОК", new string[3] { "Ваше убийство: 20% шанс вернуть один рывок.", "Ваше убийство: 30% шанс вернуть один рывок.", "Ваше убийство: 40% шанс вернуть один рывок." }),
			new Upgrade(UpgradeId.OnEdge, "На грани", "ГРАНЬ", new string[3] { "+35% урона при 30 HP и ниже.", "+50% урона при 30 HP и ниже.", "+65% урона при 30 HP и ниже." }),
			new Upgrade(UpgradeId.CloseCombat, "Ближний охотник", "ВБЛИЗИ", new string[3] { "+20% урона по целям в пределах 8 м.", "+30% урона по целям в пределах 8 м.", "+40% урона по целям в пределах 8 м." }),
			new Upgrade(UpgradeId.Armour, "Бронепластины", "БРОНЯ", new string[3] { "Обычный входящий урон снижен на 15%.", "Обычный входящий урон снижен на 20%.", "Обычный входящий урон снижен на 25%." }),
			new Upgrade(UpgradeId.BloodEngine, "Кровавый двигатель", "ДВИГАТЕЛЬ", new string[1] { "Каждое прямое добивание кулаком гарантированно возвращает 30 выносливости. Дополняет обычный шанс рывка." }, new UpgradeId[2]
			{
				UpgradeId.Bloodlust,
				UpgradeId.SpareDash
			}),
			new Upgrade(UpgradeId.FallenAngel, "Падший ангел", "АНГЕЛ", new string[1] { "Ваше убийство в воздухе при 30 HP и ниже восстанавливает 8 HP. Условия фиксируются при попадании." }, new UpgradeId[2]
			{
				UpgradeId.AirAce,
				UpgradeId.OnEdge
			}),
			new Upgrade(UpgradeId.Epicenter, "Эпицентр", "ЭПИЦЕНТР", new string[1] { "Ваши взрывы дополнительно наносят +35% урона врагам в пределах 8 м от вас. Радиус и урон себе не меняются." }, new UpgradeId[2]
			{
				UpgradeId.BlastCharge,
				UpgradeId.CloseCombat
			}),
			new Upgrade(UpgradeId.Assault, "Первый натиск", "НАТИСК", new string[1] { "+15% вашего урона только на следующую волну. Временная награда, не имеет уровней." }, null, tactical: true),
			new Upgrade(UpgradeId.FieldShield, "Полевой щит", "ЩИТ", new string[1] { "Ещё -10% обычного входящего урона только на следующую волну. Смертельные зоны не ослабляются." }, null, tactical: true),
			new Upgrade(UpgradeId.DashReserve, "Резерв рывка", "РЕЗЕРВ", new string[1] { "Три ваших убийства при неполной выносливости на следующей волне возвращают по одному рывку." }, null, tactical: true),
			new Upgrade(UpgradeId.GlassCannon, "Стеклянный корпус", "СТЕКЛО", new string[1] { "+40% вашего урона. Цена: обычный входящий урон увеличен на 25%." }, null, tactical: false, cursed: true),
			new Upgrade(UpgradeId.Thirst, "Жажда", "ЖАЖДА", new string[1] { "Лечение Кровавого азарта усилено на 50%. Цена: остальное лечение ослаблено на 25%. Восстановление между волнами сохраняется." }, null, tactical: false, cursed: true),
			new Upgrade(UpgradeId.Overdrive, "Перегрузка", "ПЕРЕГРУЗКА", new string[1] { "+25% вашего урона. Цена: все бонусы выносливости от улучшений и наград мода уменьшены вдвое." }, null, tactical: false, cursed: true),
			new Upgrade(UpgradeId.LongVow, "Дальний обет", "ОБЕТ", new string[1] { "+30% урона по целям дальше 20 м. Цена: -15% вашего урона по целям в пределах 8 м." }, null, tactical: false, cursed: true)
		};

		public static Upgrade Get(UpgradeId id)
		{
			return All[(int)id];
		}

		public static string LevelName(int level)
		{
			return level switch
			{
				3 => "III", 
				2 => "II", 
				1 => "I", 
				_ => "—", 
			};
		}
	}
	public sealed class RunState
	{
		public const int BasicSlots = 6;

		public const int CurseSlots = 2;

		public const int MaxRerolls = 5;

		private readonly int[] levels = new int[Upgrades.All.Length];

		private readonly HashSet<int> completedWaves = new HashSet<int>();

		private readonly HashSet<UpgradeId> banished = new HashSet<UpgradeId>();

		private readonly HashSet<UpgradeId> offerSeen = new HashSet<UpgradeId>();

		private UpgradeId[] choices = Array.Empty<UpgradeId>();

		private bool waveHurt;

		public bool Active { get; private set; }

		public bool WaveOpen { get; private set; }

		public int CompletedCount { get; private set; }

		public int LastCompletedWave { get; private set; }

		public int ActiveWave { get; private set; }

		public int Generation { get; private set; }

		public int OfferToken { get; private set; }

		public bool ChestOffer { get; private set; }

		public int Rerolls { get; private set; }

		public int Banishes { get; private set; }

		public int Skips { get; private set; }

		public RunChallenge Challenge { get; } = new RunChallenge();

		public RunLedger Stats { get; private set; } = new RunLedger();

		public string LastChallengeResult { get; private set; } = "";

		public IReadOnlyList<UpgradeId> Choices => choices;

		public bool Choosing => choices.Length != 0;

		public int BasicCount => Upgrades.All.Count((Upgrade u) => IsBasic(u) && Has(u.Id));

		public int CurseCount => Upgrades.All.Count((Upgrade u) => u.Cursed && Has(u.Id));

		public int OwnedCount => levels.Count((int v) => v > 0);

		public int TotalLevels => levels.Sum();

		public int EvolutionCount => Upgrades.All.Count((Upgrade u) => u.Evolution && Has(u.Id));

		public int WavesUntilChoice => 5 - CompletedCount % 5;

		public int WavesUntilChest => 10 - CompletedCount % 10;

		public UpgradeId? Tactical { get; private set; }

		public int TacticalWave { get; private set; }

		public int ReserveCharges { get; private set; }

		public int Level(UpgradeId id)
		{
			if (!Active || id < UpgradeId.Bloodlust || (int)id >= levels.Length)
			{
				return 0;
			}
			return levels[(int)id];
		}

		public bool Has(UpgradeId id)
		{
			return Level(id) > 0;
		}

		public bool IsBanished(UpgradeId id)
		{
			return banished.Contains(id);
		}

		public bool TacticalActive(UpgradeId id)
		{
			if (Active && WaveOpen && Tactical == id && TacticalWave == ActiveWave)
			{
				return TacticalWave > 0;
			}
			return false;
		}

		public static bool IsBasic(Upgrade u)
		{
			if (!u.Evolution && !u.Cursed)
			{
				return !u.Tactical;
			}
			return false;
		}

		public bool RecipeReady(UpgradeId id)
		{
			Upgrade upgrade = Upgrades.Get(id);
			if (Active && upgrade.Evolution && !IsBanished(id) && !Has(id) && Level(upgrade.Parents[0]) >= 3)
			{
				return Level(upgrade.Parents[1]) >= 1;
			}
			return false;
		}

		public bool CanOffer(UpgradeId id)
		{
			Upgrade upgrade = Upgrades.Get(id);
			if (!Active || upgrade.Tactical || IsBanished(id) || Level(id) >= upgrade.MaxLevel)
			{
				return false;
			}
			if (upgrade.Evolution)
			{
				if (ChestOffer)
				{
					return RecipeReady(id);
				}
				return false;
			}
			if (upgrade.Cursed)
			{
				if (ChestOffer && CurseCount < 2)
				{
					if (id == UpgradeId.Thirst)
					{
						return Has(UpgradeId.Bloodlust);
					}
					return true;
				}
				return false;
			}
			if (!Has(id))
			{
				return BasicCount < 6;
			}
			return true;
		}

		public void Begin(Random random = null)
		{
			Reset();
			Active = true;
			Rerolls = 3;
			int banishes = (Skips = 2);
			Banishes = banishes;
			Challenge.Start((ChallengeKind)(random ?? new Random()).Next(3));
		}

		public void Reset()
		{
			Generation++;
			OfferToken++;
			bool flag = (ChestOffer = (waveHurt = false));
			bool active = (WaveOpen = flag);
			Active = active;
			int num = (Skips = 0);
			int num3 = (Banishes = num);
			int num5 = (Rerolls = num3);
			int num7 = (ActiveWave = num5);
			int completedCount = (LastCompletedWave = num7);
			CompletedCount = completedCount;
			Array.Clear(levels, 0, levels.Length);
			completedWaves.Clear();
			banished.Clear();
			offerSeen.Clear();
			choices = Array.Empty<UpgradeId>();
			Tactical = null;
			completedCount = (ReserveCharges = 0);
			TacticalWave = completedCount;
			Challenge.Reset();
			LastChallengeResult = "";
			Stats = new RunLedger();
		}

		public void WaveStarted(int wave)
		{
			if (Active && wave > 0 && (!WaveOpen || ActiveWave != wave))
			{
				ActiveWave = wave;
				WaveOpen = true;
				waveHurt = false;
				if (Tactical.HasValue && TacticalWave == 0)
				{
					TacticalWave = wave;
				}
			}
		}

		public bool CompleteWave(int wave, Random random)
		{
			if (!Active || Choosing || !WaveOpen || wave <= 0 || wave != ActiveWave || !completedWaves.Add(wave))
			{
				return false;
			}
			WaveOpen = false;
			if (TacticalWave == wave)
			{
				Tactical = null;
				int tacticalWave = (ReserveCharges = 0);
				TacticalWave = tacticalWave;
			}
			CompletedCount++;
			LastCompletedWave = wave;
			bool flag = Challenge.FinishWave(waveHurt);
			if (CompletedCount % 5 != 0)
			{
				return false;
			}
			if (flag)
			{
				Stats.ChallengesWon++;
				int rerolls = Rerolls;
				Rerolls = Math.Min(5, Rerolls + 1);
				LastChallengeResult = "ИСПЫТАНИЕ ВЫПОЛНЕНО / " + ((Rerolls > rerolls) ? "+1 ПЕРЕБРОС" : "ПЕРЕБРОСЫ 5/5");
			}
			else
			{
				LastChallengeResult = "Испытание завершено без награды. Новое задание — на следующие пять волн.";
			}
			Challenge.Start((ChallengeKind)random.Next(3));
			ChestOffer = CompletedCount % 10 == 0;
			offerSeen.Clear();
			SetOffer(GenerateOffer(random, (IReadOnlyCollection<UpgradeId>)(object)Array.Empty<UpgradeId>()));
			return true;
		}

		private UpgradeId[] GenerateOffer(Random random, IReadOnlyCollection<UpgradeId> avoid)
		{
			List<UpgradeId> list = (from u in Upgrades.All
				where !u.Cursed && CanOffer(u.Id)
				select u.Id).ToList();
			Shuffle(list, random);
			list = list.OrderBy((UpgradeId id) => avoid.Contains(id)).ToList();
			List<UpgradeId> offer = new List<UpgradeId>();
			UpgradeId[] array = list.Where((UpgradeId id) => Upgrades.Get(id).Evolution).ToArray();
			if (array.Length != 0)
			{
				offer.Add(array[0]);
			}
			List<UpgradeId> list2 = (from u in Upgrades.All
				where u.Cursed && CanOffer(u.Id)
				select u.Id).ToList();
			Shuffle(list2, random);
			list2 = list2.OrderBy((UpgradeId id) => avoid.Contains(id)).ToList();
			if (list2.Count > 0 && random.NextDouble() < 0.5)
			{
				offer.Add(list2[0]);
			}
			offer.AddRange(list.Where((UpgradeId id) => !offer.Contains(id)).Take(3 - offer.Count));
			List<UpgradeId> list3 = (from u in Upgrades.All
				where u.Tactical
				select u.Id).ToList();
			Shuffle(list3, random);
			list3 = list3.OrderBy((UpgradeId id) => avoid.Contains(id)).ToList();
			offer.AddRange(list3.Take(3 - offer.Count));
			return offer.ToArray();
		}

		private void SetOffer(UpgradeId[] offer)
		{
			choices = offer;
			OfferToken++;
			foreach (UpgradeId item in offer)
			{
				offerSeen.Add(item);
			}
		}

		private bool ValidOffer(int generation, int token)
		{
			if (Active && Choosing && generation == Generation)
			{
				return token == OfferToken;
			}
			return false;
		}

		private void CloseOffer()
		{
			choices = Array.Empty<UpgradeId>();
			ChestOffer = false;
			OfferToken++;
		}

		private static void Shuffle(List<UpgradeId> list, Random random)
		{
			for (int num = list.Count - 1; num > 0; num--)
			{
				int index = random.Next(num + 1);
				UpgradeId value = list[num];
				list[num] = list[index];
				list[index] = value;
			}
		}

		public bool TryChoose(int index, int generation, int token, out UpgradeId selected)
		{
			selected = UpgradeId.Bloodlust;
			if (!ValidOffer(generation, token) || index < 0 || index >= choices.Length)
			{
				return false;
			}
			selected = choices[index];
			Upgrade upgrade = Upgrades.Get(selected);
			if (!upgrade.Tactical && !CanOffer(selected))
			{
				return false;
			}
			CloseOffer();
			Stats.Choices++;
			if (upgrade.Tactical)
			{
				Tactical = selected;
				TacticalWave = 0;
				ReserveCharges = ((selected == UpgradeId.DashReserve) ? 3 : 0);
			}
			else
			{
				levels[(int)selected]++;
			}
			return true;
		}

		public bool TryReroll(int generation, int token, Random random)
		{
			if (!ValidOffer(generation, token) || Rerolls <= 0)
			{
				return false;
			}
			UpgradeId[] array = choices.ToArray();
			for (int i = 0; i < 20; i++)
			{
				UpgradeId[] array2 = GenerateOffer(random, offerSeen);
				if (!array2.All(((IEnumerable<UpgradeId>)array).Contains<UpgradeId>))
				{
					SetOffer(array2);
					Rerolls--;
					Stats.RerollsUsed++;
					return true;
				}
			}
			return false;
		}

		public bool TryBanish(int index, int generation, int token, Random random)
		{
			if (!ValidOffer(generation, token) || Banishes <= 0 || index < 0 || index >= choices.Length)
			{
				return false;
			}
			UpgradeId upgradeId = choices[index];
			if (Upgrades.Get(upgradeId).Tactical || !banished.Add(upgradeId))
			{
				return false;
			}
			Banishes--;
			Stats.BanishesUsed++;
			SetOffer(GenerateOffer(random, offerSeen));
			return true;
		}

		public bool TrySkip(int generation, int token)
		{
			if (!ValidOffer(generation, token) || Skips <= 0)
			{
				return false;
			}
			CloseOffer();
			Skips--;
			Stats.SkipsUsed++;
			return true;
		}

		public void RecordKill(bool melee, bool airborne)
		{
			if (Active && WaveOpen)
			{
				Stats.Kills++;
				if (melee)
				{
					Stats.MeleeKills++;
				}
				if (airborne)
				{
					Stats.AirKills++;
				}
				Challenge.Kill(melee, airborne);
			}
		}

		public void RecordHurt(int hpLost)
		{
			if (Active && WaveOpen && hpLost > 0)
			{
				waveHurt = true;
			}
		}

		public void RecordHealing(int amount)
		{
			if (Active && amount > 0)
			{
				Stats.Healing += amount;
			}
		}

		public void RecordStamina(float amount)
		{
			if (Active && amount > 0f)
			{
				Stats.Stamina += amount;
			}
		}

		public void Tick(float seconds)
		{
			if (Active && WaveOpen && seconds > 0f)
			{
				Stats.Seconds += seconds;
			}
		}

		public RunSummary Snapshot(string reason)
		{
			return new RunSummary(this, reason);
		}

		public bool ConsumeReserve()
		{
			if (!TacticalActive(UpgradeId.DashReserve) || ReserveCharges <= 0)
			{
				return false;
			}
			ReserveCharges--;
			return true;
		}
	}
	public static class UpgradeMath
	{
		public static int BloodHeal(int rank)
		{
			if (rank > 0)
			{
				return rank switch
				{
					2 => 8, 
					1 => 5, 
					_ => 12, 
				};
			}
			return 0;
		}

		public static float RhythmDuration(int rank)
		{
			if (rank > 0)
			{
				return 1.5f + 0.5f * (float)Math.Min(rank, 3);
			}
			return 0f;
		}

		public static float DashChance(int rank)
		{
			if (rank > 0)
			{
				return 0.1f + 0.1f * (float)Math.Min(rank, 3);
			}
			return 0f;
		}

		public static float RhythmBonus(int rank)
		{
			if (rank > 0)
			{
				return rank switch
				{
					2 => 0.35f, 
					1 => 0.25f, 
					_ => 0.45f, 
				};
			}
			return 0f;
		}

		public static float AirBonus(int rank)
		{
			if (rank > 0)
			{
				return rank switch
				{
					2 => 0.3f, 
					1 => 0.2f, 
					_ => 0.4f, 
				};
			}
			return 0f;
		}

		public static float BlastBonus(int rank)
		{
			if (rank > 0)
			{
				return rank switch
				{
					2 => 0.35f, 
					1 => 0.25f, 
					_ => 0.5f, 
				};
			}
			return 0f;
		}

		public static float EdgeBonus(int rank)
		{
			if (rank > 0)
			{
				return rank switch
				{
					2 => 0.5f, 
					1 => 0.35f, 
					_ => 0.65f, 
				};
			}
			return 0f;
		}

		public static float ArmourReduction(int rank)
		{
			if (rank > 0)
			{
				return rank switch
				{
					2 => 0.2f, 
					1 => 0.15f, 
					_ => 0.25f, 
				};
			}
			return 0f;
		}

		public static float DamageFactor(RunState run, bool rhythm, bool airborne, bool blast, bool edge, bool close, bool far = false)
		{
			return 1f + (rhythm ? RhythmBonus(run.Level(UpgradeId.Rhythm)) : 0f) + (airborne ? AirBonus(run.Level(UpgradeId.AirAce)) : 0f) + (blast ? BlastBonus(run.Level(UpgradeId.BlastCharge)) : 0f) + (edge ? EdgeBonus(run.Level(UpgradeId.OnEdge)) : 0f) + (close ? AirBonus(run.Level(UpgradeId.CloseCombat)) : 0f) + ((blast && close && run.Has(UpgradeId.Epicenter)) ? 0.35f : 0f) + (run.TacticalActive(UpgradeId.Assault) ? 0.15f : 0f) + (run.Has(UpgradeId.GlassCannon) ? 0.4f : 0f) + (run.Has(UpgradeId.Overdrive) ? 0.25f : 0f) + ((!run.Has(UpgradeId.LongVow)) ? 0f : (far ? 0.3f : (close ? (-0.15f) : 0f)));
		}

		public static int ArmourDamage(int damage, bool instakill, int rank, bool shield, bool glass = false)
		{
			if (damage <= 0 || instakill)
			{
				return damage;
			}
			int num = ((rank > 0) ? (rank switch
			{
				2 => 20, 
				1 => 15, 
				_ => 25, 
			}) : 0);
			long num2 = (long)damage * (long)(100 - num - (shield ? 10 : 0)) * (glass ? 125 : 100);
			return (int)Math.Min(2147483647L, Math.Max(1L, (num2 + 9999) / 10000));
		}

		public static float RestoreDash(float current, float amount = 100f)
		{
			return Math.Min(300f, current + amount);
		}
	}
}