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);
}
}
}