using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Cruciball;
using HarmonyLib;
using Loading;
using Microsoft.CodeAnalysis;
using Peglin.ClassSystem;
using PeglinQuickRestart.Core;
using PeglinUI.LoadoutManager;
using PeglinUI.MainMenu;
using PeglinUI.MainMenu.Cruciball;
using Relics;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("PeglinQuickRestart")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+9d0e5c8cd1a8420e6dbe59e1dc2f8dd9ce57bea0")]
[assembly: AssemblyProduct("PeglinQuickRestart")]
[assembly: AssemblyTitle("PeglinQuickRestart")]
[assembly: AssemblyVersion("1.1.0.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 PeglinQuickRestart
{
[BepInPlugin("PeglinQuickRestart", "PeglinQuickRestart", "1.1.0")]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "PeglinQuickRestart";
public const string Name = "PeglinQuickRestart";
public const string Version = "1.1.0";
internal static ManualLogSource Log;
internal static ConfigEntry<KeyboardShortcut> RestartKey;
internal static ConfigEntry<KeyboardShortcut> ControllerRestartButton;
internal static ConfigEntry<float> HoldSeconds;
internal static ConfigEntry<bool> ShowHoldProgress;
private void Awake()
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: 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_00d0: Unknown result type (might be due to invalid IL or missing references)
//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
//IL_00de: Expected O, but got Unknown
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
Log = ((BaseUnityPlugin)this).Logger;
RestartKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "RestartKey", new KeyboardShortcut((KeyCode)120, Array.Empty<KeyCode>()), "Hold this key to abandon the current run and start a new one with the same class, Cruciball level and loadout. A run started with a custom seed restarts on that seed; any other run gets a new random seed.");
ControllerRestartButton = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ControllerRestartButton", new KeyboardShortcut((KeyCode)338, Array.Empty<KeyCode>()), "Controller button to hold for a restart. Default JoystickButton8 is clicking the left stick (L3) on Xbox-layout controllers, including PlayStation and other pads when Steam Input is on. Set to None to disable.");
HoldSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "HoldSeconds", 1.5f, "How long to hold the restart key, in real seconds (not affected by game speed or pause).");
ShowHoldProgress = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowHoldProgress", true, "Show a countdown at the top of the screen while the restart key is held.");
new Harmony("PeglinQuickRestart").PatchAll(typeof(Plugin).Assembly);
GameObject val = new GameObject("PeglinQuickRestart")
{
hideFlags = (HideFlags)61
};
Object.DontDestroyOnLoad((Object)val);
val.AddComponent<RestartKeyWatcher>();
Log.LogInfo((object)string.Format("{0} {1} loaded. Hold {2} or {3} for {4}s to restart the run.", "PeglinQuickRestart", "1.1.0", RestartKey.Value, ControllerRestartButton.Value, HoldSeconds.Value));
}
}
internal class RestartKeyWatcher : MonoBehaviour
{
private HoldTimer _hold;
private RunRestarter _restarter;
private GUIStyle _style;
private void Awake()
{
_hold = new HoldTimer(Plugin.HoldSeconds.Value);
Plugin.HoldSeconds.SettingChanged += delegate
{
_hold = new HoldTimer(Plugin.HoldSeconds.Value);
};
_restarter = new RunRestarter((MonoBehaviour)(object)this);
}
private void Update()
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
bool keyHeld = !_restarter.Busy && (IsHeld(Plugin.RestartKey.Value) || IsHeld(Plugin.ControllerRestartButton.Value)) && _restarter.InRestartableScene();
if (_hold.Update(keyHeld, Time.unscaledDeltaTime))
{
_restarter.TryRestart();
}
}
private static bool IsHeld(KeyboardShortcut shortcut)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0)
{
return ((KeyboardShortcut)(ref shortcut)).IsPressed();
}
return false;
}
private void OnGUI()
{
//IL_00be: 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_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_009d: Expected O, but got Unknown
string text = (_restarter.Busy ? "Restarting run..." : _restarter.Notice);
if (text == null && Plugin.ShowHoldProgress.Value && _hold.Charging)
{
text = $"Hold to restart run: {Mathf.Max(0f, _hold.HoldSeconds - _hold.Held):0.0}s";
}
if (text != null)
{
if (_style == null)
{
_style = new GUIStyle(GUI.skin.box)
{
fontSize = 22,
alignment = (TextAnchor)4
};
}
GUI.Box(new Rect((float)Screen.width / 2f - 260f, 20f, 520f, 44f), text, _style);
}
}
}
internal sealed class RunRestarter
{
private const string MainMenuScene = "MainMenu";
private const float SceneLoadTimeout = 30f;
private const float MenuTimeout = 10f;
private const float NoticeSeconds = 4f;
private static readonly FieldRef<CharacterSelectController, List<SelectableCharacter>> AvailableCharacters = AccessTools.FieldRefAccess<CharacterSelectController, List<SelectableCharacter>>("_availableCharacters");
private static readonly FieldRef<CharacterSelectController, bool> ClassesLoaded = AccessTools.FieldRefAccess<CharacterSelectController, bool>("_classesLoaded");
private static readonly FieldRef<LoadoutManager, DeckManager> LoadoutDeckManager = AccessTools.FieldRefAccess<LoadoutManager, DeckManager>("_deckManager");
private static readonly FieldRef<LoadoutManager, RelicManager> LoadoutRelicManager = AccessTools.FieldRefAccess<LoadoutManager, RelicManager>("_relicManager");
private static readonly MethodInfo ChangeClass = AccessTools.Method(typeof(CharacterSelectController), "ChangeClass", new Type[1] { typeof(SelectableCharacter) }, (Type[])null);
private readonly MonoBehaviour _host;
private string _notice;
private float _noticeUntil;
internal bool Busy { get; private set; }
internal string Notice
{
get
{
if (!(Time.unscaledTime < _noticeUntil))
{
return null;
}
return _notice;
}
}
internal RunRestarter(MonoBehaviour host)
{
_host = host;
}
internal bool InRestartableScene()
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
PeglinSceneLoader val = Object.FindObjectOfType<PeglinSceneLoader>();
if ((Object)(object)val != (Object)null && !val.isLoading)
{
Scene activeScene = SceneManager.GetActiveScene();
return ((Scene)(ref activeScene)).name != "MainMenu";
}
return false;
}
internal void TryRestart()
{
RunSetupRecord runSetupRecord = RunSetup.ForCurrentRun();
if (runSetupRecord == null)
{
Show("Can't restart: this run's custom setup isn't known");
Plugin.Log.LogWarning((object)"Can't restart: no run in progress, or a custom run started before the mod was installed.");
}
else if (!Enum.IsDefined(typeof(Class), runSetupRecord.Class))
{
Show("Can't restart: unknown class " + runSetupRecord.Class);
}
else
{
_host.StartCoroutine(Restart(runSetupRecord));
}
}
private IEnumerator Restart(RunSetupRecord setup)
{
Busy = true;
Plugin.Log.LogInfo((object)($"Restarting run: {setup.Class}, Cruciball {setup.CruciballLevel}, " + (setup.CustomSeed ? ("seed " + setup.Seed) : "random seed") + (setup.CustomLoadout ? ", custom loadout" : "") + "."));
AbandonRun();
float waited = 0f;
PeglinSceneLoader val = Object.FindObjectOfType<PeglinSceneLoader>();
while (true)
{
if (!((Object)(object)val == (Object)null) && !val.isLoading)
{
Scene activeScene = SceneManager.GetActiveScene();
if (!(((Scene)(ref activeScene)).name != "MainMenu"))
{
break;
}
}
float num;
waited = (num = waited + Time.unscaledDeltaTime);
if (num > 30f)
{
Fail("the main menu didn't load");
yield break;
}
yield return null;
val = Object.FindObjectOfType<PeglinSceneLoader>();
}
PlayButton play = Object.FindObjectOfType<PlayButton>();
if ((Object)(object)play == (Object)null)
{
Fail("couldn't find the Play button");
yield break;
}
play.MovetoCharacterSelect();
waited = 0f;
LoadoutManager loadout;
CharacterSelectController characters;
CruciballLevelSelector cruciballSelector;
while (true)
{
loadout = Object.FindObjectOfType<LoadoutManager>();
characters = (((Object)(object)loadout != (Object)null) ? loadout.characterSelectController : null);
cruciballSelector = Object.FindObjectOfType<CruciballLevelSelector>();
if (((Component)play._characterSelectGroup).gameObject.activeInHierarchy && (Object)(object)loadout != (Object)null && (Object)(object)characters != (Object)null && ClassesLoaded.Invoke(characters) && (Object)(object)cruciballSelector != (Object)null)
{
break;
}
float num;
waited = (num = waited + Time.unscaledDeltaTime);
if (num > 10f)
{
Fail("the character select screen didn't open");
yield break;
}
yield return null;
}
yield return null;
string text = ApplySetup(setup, loadout, characters);
if (text != null)
{
Fail(text);
yield break;
}
if ((Object)(object)cruciballSelector.playButton != (Object)null && ((UnityEventBase)cruciballSelector.playButton.onClick).GetPersistentEventCount() > 0)
{
((UnityEvent)cruciballSelector.playButton.onClick).Invoke();
}
else
{
loadout.SetupDataForNewGame();
play.ConfirmRunConfigAndStartGame();
}
Busy = false;
}
private static void AbandonRun()
{
PauseMenu val = Object.FindObjectOfType<PauseMenu>();
if ((Object)(object)val != (Object)null)
{
val.AbandonRun();
return;
}
if (!StaticGameData.hasReachedBoss)
{
PlayerPrefs.SetInt(GameInit.BOSS_STREAK_PREF_STRING, 0);
}
if (PauseMenu.Paused)
{
PauseMenu.Paused = false;
PauseToggle onPauseToggled = PauseMenu.OnPauseToggled;
if (onPauseToggled != null)
{
onPauseToggled.Invoke(false);
}
}
SaveManager.RequestDelete();
PeglinSceneLoader.Instance.LoadScene((Scene)1, (LoadSceneMode)0, false, 0.5f);
}
private static string ApplySetup(RunSetupRecord setup, LoadoutManager loadout, CharacterSelectController characters)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: 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_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_00d5: 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)
Class cls = (Class)Enum.Parse(typeof(Class), setup.Class);
SelectableCharacter val = ((IEnumerable<SelectableCharacter>)AvailableCharacters.Invoke(characters)).FirstOrDefault((Func<SelectableCharacter, bool>)((SelectableCharacter c) => c.characterClass == cls));
if ((Object)(object)val == (Object)null || !val.IsUnlocked)
{
return setup.Class + " isn't available";
}
if (characters.currentlySelectedClass != cls)
{
ChangeClass.Invoke(characters, new object[1] { val });
}
if (characters.currentlySelectedClass != cls)
{
return "couldn't select " + setup.Class;
}
CruciballManager val2 = RunSetup.LoadoutCruciballManager.Invoke(loadout);
if ((Object)(object)val2 != (Object)null && val2.currentCruciballLevel != setup.CruciballLevel)
{
val2.SetRunCruciballLevelAndClass(setup.CruciballLevel, cls);
}
if (setup.CustomLoadout)
{
DeckManager val3 = LoadoutDeckManager.Invoke(loadout);
RelicManager val4 = LoadoutRelicManager.Invoke(loadout);
List<GameObject> list = new List<GameObject>();
foreach (string orb in setup.Orbs)
{
GameObject val5 = (((Object)(object)val3 != (Object)null) ? val3.GetOrbPrefabFromName(orb) : null);
if ((Object)(object)val5 == (Object)null)
{
return "couldn't find starting orb " + orb;
}
list.Add(val5);
}
List<Relic> list2 = new List<Relic>();
foreach (string relic in setup.Relics)
{
Relic val6 = (((Object)(object)val4 != (Object)null && Enum.IsDefined(typeof(RelicEffect), relic)) ? val4.GetRelicForEffectFromAllData((RelicEffect)Enum.Parse(typeof(RelicEffect), relic)) : null);
if ((Object)(object)val6 == (Object)null)
{
return "couldn't find starting relic " + relic;
}
list2.Add(val6);
}
loadout.hasCustomLoadout = true;
loadout.StartingOrbs.Clear();
loadout.StartingOrbs.AddRange(list);
loadout.StartingRelics.Clear();
loadout.StartingRelics.AddRange(list2);
}
loadout.seedInputPopup.hasCustomSeed = setup.CustomSeed;
StaticGameData.currentSeed = (setup.CustomSeed ? setup.Seed : string.Empty);
return null;
}
private void Fail(string reason)
{
Busy = false;
Show("Restart stopped: " + reason);
Plugin.Log.LogWarning((object)("Restart stopped at the main menu: " + reason + "."));
}
private void Show(string message)
{
_notice = message;
_noticeUntil = Time.unscaledTime + 4f;
}
}
internal static class RunSetup
{
private static readonly string SavePath = Path.Combine(Paths.ConfigPath, "PeglinQuickRestart.lastrun.txt");
internal static readonly FieldRef<LoadoutManager, CruciballManager> LoadoutCruciballManager = AccessTools.FieldRefAccess<LoadoutManager, CruciballManager>("_cruciballManager");
internal static RunSetupRecord Pending;
private static RunSetupRecord _current;
internal static void RecordPlay(LoadoutManager loadout)
{
CruciballManager val = LoadoutCruciballManager.Invoke(loadout);
RunSetupRecord runSetupRecord = new RunSetupRecord
{
Class = ((object)Unsafe.As<Class, Class>(ref StaticGameData.chosenClass)/*cast due to .constrained prefix*/).ToString(),
CruciballLevel = (((Object)(object)val != (Object)null) ? val.currentCruciballLevel : 0),
CustomSeed = loadout.seedInputPopup.hasCustomSeed,
Seed = (loadout.seedInputPopup.hasCustomSeed ? StaticGameData.currentSeed : ""),
CustomLoadout = loadout.hasCustomLoadout
};
if (runSetupRecord.CustomLoadout)
{
runSetupRecord.Orbs = loadout.StartingOrbs.Select((GameObject orb) => ((Object)orb).name).ToList();
runSetupRecord.Relics = loadout.StartingRelics.Select((Relic relic) => ((object)Unsafe.As<RelicEffect, RelicEffect>(ref relic.effect)/*cast due to .constrained prefix*/).ToString()).ToList();
}
Pending = runSetupRecord;
}
internal static void RunStarted()
{
if (Pending == null)
{
return;
}
Pending.RunSeed = StaticGameData.currentSeed;
_current = Pending;
Pending = null;
try
{
File.WriteAllText(SavePath, _current.Serialize());
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Couldn't save the run setup to " + SavePath + ": " + ex.Message));
}
}
internal static RunSetupRecord ForCurrentRun()
{
string currentSeed = StaticGameData.currentSeed;
if (string.IsNullOrEmpty(currentSeed))
{
return null;
}
if (_current != null && _current.IsForRun(currentSeed))
{
return _current;
}
RunSetupRecord runSetupRecord = Load();
if (runSetupRecord != null && runSetupRecord.IsForRun(currentSeed))
{
_current = runSetupRecord;
return runSetupRecord;
}
if (StaticGameData.CurrentRunStats != null && !StaticGameData.CurrentRunStats.isCustomRun)
{
CruciballManager val = Resources.FindObjectsOfTypeAll<CruciballManager>().FirstOrDefault();
return new RunSetupRecord
{
Class = ((object)Unsafe.As<Class, Class>(ref StaticGameData.chosenClass)/*cast due to .constrained prefix*/).ToString(),
CruciballLevel = (((Object)(object)val != (Object)null) ? val.currentCruciballLevel : 0),
RunSeed = currentSeed
};
}
return null;
}
private static RunSetupRecord Load()
{
try
{
return File.Exists(SavePath) ? RunSetupRecord.Parse(File.ReadAllText(SavePath)) : null;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Couldn't read the run setup from " + SavePath + ": " + ex.Message));
return null;
}
}
}
[HarmonyPatch(typeof(PlayButton), "ConfirmRunConfigAndStartGame")]
internal static class RecordSetupOnPlayPatch
{
private static void Prefix()
{
LoadoutManager val = Object.FindObjectOfType<LoadoutManager>();
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"Couldn't find the loadout screen when the run started, so this run can't be restarted with the same setup.");
RunSetup.Pending = null;
}
else
{
RunSetup.RecordPlay(val);
}
}
}
[HarmonyPatch(typeof(GameInit), "Start")]
internal static class RecordSeedOnRunStartPatch
{
private static readonly FieldRef<GameInit, LoadMapData> LoadData = AccessTools.FieldRefAccess<GameInit, LoadMapData>("LoadData");
private static void Postfix(GameInit __instance)
{
LoadMapData val = LoadData.Invoke(__instance);
if ((Object)(object)val != (Object)null && val.NewGame)
{
RunSetup.RunStarted();
}
}
}
}
namespace PeglinQuickRestart.Core
{
public sealed class HoldTimer
{
private bool _fired;
public float HoldSeconds { get; }
public float Held { get; private set; }
public float Progress
{
get
{
if (!_fired)
{
if (!(HoldSeconds <= 0f))
{
return Math.Min(1f, Held / HoldSeconds);
}
return 0f;
}
return 1f;
}
}
public bool Charging
{
get
{
if (Held > 0f)
{
return !_fired;
}
return false;
}
}
public HoldTimer(float holdSeconds)
{
HoldSeconds = Math.Max(0f, holdSeconds);
}
public bool Update(bool keyHeld, float deltaTime)
{
if (!keyHeld)
{
Held = 0f;
_fired = false;
return false;
}
if (_fired)
{
return false;
}
Held += Math.Max(0f, deltaTime);
if (Held >= HoldSeconds)
{
_fired = true;
return true;
}
return false;
}
}
public sealed class RunSetupRecord
{
public string Class = "";
public int CruciballLevel;
public bool CustomSeed;
public string Seed = "";
public bool CustomLoadout;
public List<string> Orbs = new List<string>();
public List<string> Relics = new List<string>();
public string RunSeed = "";
public bool IsForRun(string currentSeed)
{
if (!string.IsNullOrEmpty(RunSeed))
{
return RunSeed == currentSeed;
}
return false;
}
public string Serialize()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("Class=").Append(Class).Append('\n');
stringBuilder.Append("CruciballLevel=").Append(CruciballLevel).Append('\n');
stringBuilder.Append("CustomSeed=").Append(CustomSeed).Append('\n');
stringBuilder.Append("Seed=").Append(Seed).Append('\n');
stringBuilder.Append("CustomLoadout=").Append(CustomLoadout).Append('\n');
stringBuilder.Append("Orbs=").Append(string.Join(",", Orbs)).Append('\n');
stringBuilder.Append("Relics=").Append(string.Join(",", Relics)).Append('\n');
stringBuilder.Append("RunSeed=").Append(RunSeed).Append('\n');
return stringBuilder.ToString();
}
public static RunSetupRecord Parse(string text)
{
if (string.IsNullOrEmpty(text))
{
return null;
}
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string[] array = text.Split(new char[1] { '\n' });
foreach (string text2 in array)
{
int num = text2.IndexOf('=');
if (num > 0)
{
dictionary[text2.Substring(0, num).Trim()] = text2.Substring(num + 1).Trim();
}
}
if (!dictionary.TryGetValue("Class", out var value) || value.Length == 0 || !dictionary.TryGetValue("CruciballLevel", out var value2) || !int.TryParse(value2, out var result) || !dictionary.TryGetValue("CustomSeed", out var value3) || !bool.TryParse(value3, out var result2) || !dictionary.TryGetValue("CustomLoadout", out var value4) || !bool.TryParse(value4, out var result3) || !dictionary.TryGetValue("RunSeed", out var value5) || value5.Length == 0)
{
return null;
}
dictionary.TryGetValue("Seed", out var value6);
if (result2 && string.IsNullOrEmpty(value6))
{
return null;
}
dictionary.TryGetValue("Orbs", out var value7);
dictionary.TryGetValue("Relics", out var value8);
return new RunSetupRecord
{
Class = value,
CruciballLevel = result,
CustomSeed = result2,
Seed = (result2 ? value6 : ""),
CustomLoadout = result3,
Orbs = SplitList(value7),
Relics = SplitList(value8),
RunSeed = value5
};
}
private static List<string> SplitList(string value)
{
if (string.IsNullOrEmpty(value))
{
return new List<string>();
}
return (from item in value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
select item.Trim()).ToList();
}
}
}