Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SilksongTweaks v1.0.0
BepInEx/plugins/SilksongTweaks.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using SilksongTweaks.Modules; using SilksongTweaks.Rules; using SilksongTweaks.Ui; using UnityEngine; [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("SilksongTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+58ec265190fc9f177178d92ee57f7273a21c6c75")] [assembly: AssemblyProduct("SilksongTweaks")] [assembly: AssemblyTitle("SilksongTweaks")] [assembly: AssemblyVersion("1.0.0.0")] namespace SilksongTweaks { public enum HookKind { Method, PropertyGetter, Field } public sealed class HookTarget { public string DeclaringType { get; } public string Member { get; } public HookKind Kind { get; } public string UsedBy { get; } public HookTarget(string declaringType, string member, HookKind kind, string usedBy) { DeclaringType = declaringType; Member = member; Kind = kind; UsedBy = usedBy; } public override string ToString() { return $"{DeclaringType}.{Member} ({Kind}, for {UsedBy})"; } } public static class HookTargets { public static readonly HookTarget HeroRespawned = new HookTarget("HeroController", "HeroRespawned", HookKind.Method, "ReturnToDeath"); public static readonly HookTarget Die = new HookTarget("HeroController", "Die", HookKind.Method, "ReturnToDeath, KeepRosaries"); public static readonly HookTarget BeginSceneTransition = new HookTarget("GameManager", "BeginSceneTransition", HookKind.Method, "ReturnToDeath"); public static readonly HookTarget SetHazardRespawn = new HookTarget("HeroController", "SetHazardRespawn", HookKind.Method, "ReturnToDeath"); public static readonly HookTarget CocoonBroken = new HookTarget("HeroController", "CocoonBroken", HookKind.Method, "KeepRosaries"); public static readonly HookTarget CorpseMoneyPool = new HookTarget("PlayerData", "HeroCorpseMoneyPool", HookKind.Field, "KeepRosaries"); public static readonly HookTarget CurrentMaxHealth = new HookTarget("PlayerData", "CurrentMaxHealth", HookKind.PropertyGetter, "MaxHealth"); public static readonly HookTarget GetInt = new HookTarget("PlayerData", "GetInt", HookKind.Method, "MaxHealth"); public static readonly HookTarget MaxHealthField = new HookTarget("PlayerData", "maxHealth", HookKind.Field, "MaxHealth"); public static readonly HookTarget PlayerDataAddHealth = new HookTarget("PlayerData", "AddHealth", HookKind.Method, "MaxHealth"); public static readonly HookTarget PlayerDataTakeHealth = new HookTarget("PlayerData", "TakeHealth", HookKind.Method, "MaxHealth"); public static readonly HookTarget PlayerDataMaxHealth = new HookTarget("PlayerData", "MaxHealth", HookKind.Method, "MaxHealth"); public static readonly HookTarget TakeDamage = new HookTarget("HeroController", "TakeDamage", HookKind.Method, "DamageTaken"); public static IReadOnlyList<HookTarget> All { get; } = new List<HookTarget> { HeroRespawned, Die, BeginSceneTransition, SetHazardRespawn, CocoonBroken, CorpseMoneyPool, CurrentMaxHealth, GetInt, MaxHealthField, PlayerDataAddHealth, PlayerDataTakeHealth, PlayerDataMaxHealth, TakeDamage }; } public interface ITweakModule { string Id { get; } string DisplayName { get; } string Description { get; } TweakStatus Status { get; } long LastFiredUtcTicks { get; } IReadOnlyList<ISettingRow> Settings { get; } void BindConfig(ConfigFile config); TweakStatus TryApply(Harmony harmony); } public sealed class ModuleRegistry { private readonly List<ITweakModule> _modules = new List<ITweakModule>(); private readonly ManualLogSource _log; public IReadOnlyList<ITweakModule> Modules => _modules; public int ActiveCount { get; private set; } public ModuleRegistry(ManualLogSource log) { _log = log; } public void Add(ITweakModule module) { _modules.Add(module); } public void ApplyAll(ConfigFile config, Harmony harmony) { ActiveCount = 0; foreach (ITweakModule module in _modules) { try { module.BindConfig(config); } catch (Exception arg) { _log.LogError((object)$"[{module.Id}] config binding failed: {arg}"); continue; } TweakStatus tweakStatus = module.TryApply(harmony); if (tweakStatus.State == TweakState.Unavailable) { _log.LogWarning((object)("[" + module.Id + "] UNAVAILABLE: " + tweakStatus.Reason)); continue; } ActiveCount++; _log.LogInfo((object)("[" + module.Id + "] hooked OK")); } _log.LogInfo((object)$"{ActiveCount}/{_modules.Count} tweak(s) hooked successfully"); } } [BepInPlugin("com.dloizides.silksongtweaks", "Silksong Tweaks", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.dloizides.silksongtweaks"; public const string PluginName = "Silksong Tweaks"; public const string PluginVersion = "1.0.0"; private ModuleRegistry _registry; private TweakWindow _window; private ConfigEntry<KeyCode> _toggleKey; private ConfigEntry<KeyCode> _gamepadToggleButton; private ConfigEntry<bool> _freezeWhileOpen; private bool _uiBroken; private bool _wasVisible; private float _timeScaleBeforeOpen = 1f; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; try { _toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ToggleKey", (KeyCode)289, "Opens and closes the Silksong Tweaks panel."); _gamepadToggleButton = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "GamepadToggleButton", (KeyCode)336, "Gamepad button that opens the panel. JoystickButton6 is Back/View/Share."); _freezeWhileOpen = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "FreezeGameWhileOpen", true, "Pause the game while the panel is open, so navigating it does not also move Hornet. Turn off if it conflicts with anything."); _registry = new ModuleRegistry(Log); _registry.Add(new ReturnToDeathModule()); _registry.Add(new KeepRosariesModule()); _registry.Add(new MaxHealthModule()); _registry.Add(new DamageTakenModule()); _registry.ApplyAll(((BaseUnityPlugin)this).Config, new Harmony("com.dloizides.silksongtweaks")); _window = new TweakWindow(_registry); Log.LogInfo((object)string.Format("{0} {1} ready. Press {2} for the panel.", "Silksong Tweaks", "1.0.0", _toggleKey.Value)); } catch (Exception arg) { Log.LogError((object)$"startup failed: {arg}"); } } public Coroutine Run(IEnumerator routine) { return ((MonoBehaviour)this).StartCoroutine(routine); } private void Update() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (_window != null && _toggleKey != null) { if (Input.GetKeyDown(_toggleKey.Value) || (_gamepadToggleButton != null && Input.GetKeyDown(_gamepadToggleButton.Value))) { _window.Visible = !_window.Visible; } _window.HandleInput(Time.unscaledDeltaTime); ApplyFreeze(); } } private void ApplyFreeze() { if (_freezeWhileOpen == null || !_freezeWhileOpen.Value) { _wasVisible = _window.Visible; } else if (_window.Visible != _wasVisible) { _wasVisible = _window.Visible; if (_window.Visible) { _timeScaleBeforeOpen = Time.timeScale; Time.timeScale = 0f; } else { Time.timeScale = ((_timeScaleBeforeOpen <= 0f) ? 1f : _timeScaleBeforeOpen); } } } private void OnDestroy() { if (_wasVisible && Time.timeScale == 0f) { Time.timeScale = 1f; } } private void OnGUI() { if (_window == null || _uiBroken) { return; } try { _window.Draw(); } catch (Exception arg) { _uiBroken = true; Log.LogError((object)$"UI disabled after a draw error: {arg}"); } } } public interface ISettingRow { string Label { get; } string Tooltip { get; } } public sealed class BoolRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry<bool> Entry { get; } public BoolRow(string label, string tooltip, ConfigEntry<bool> entry) { Label = label; Tooltip = tooltip; Entry = entry; } } public sealed class IntRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry<int> Entry { get; } public int Min { get; } public int Max { get; } public IntRow(string label, string tooltip, ConfigEntry<int> entry, int min, int max) { Label = label; Tooltip = tooltip; Entry = entry; Min = min; Max = max; } } public sealed class FloatRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry<float> Entry { get; } public float Min { get; } public float Max { get; } public string Format { get; } public FloatRow(string label, string tooltip, ConfigEntry<float> entry, float min, float max, string format) { Label = label; Tooltip = tooltip; Entry = entry; Min = min; Max = max; Format = format; } } public sealed class KeyRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry<KeyCode> Entry { get; } public KeyRow(string label, string tooltip, ConfigEntry<KeyCode> entry) { Label = label; Tooltip = tooltip; Entry = entry; } } public enum TweakState { Active, Disabled, Unavailable } public sealed class TweakStatus { public TweakState State { get; } public string Reason { get; } public static TweakStatus Active { get; } = new TweakStatus(TweakState.Active, string.Empty); public static TweakStatus Disabled { get; } = new TweakStatus(TweakState.Disabled, string.Empty); private TweakStatus(TweakState state, string reason) { State = state; Reason = reason; } public static TweakStatus Unavailable(string reason) { return new TweakStatus(TweakState.Unavailable, reason ?? "unknown"); } } } namespace SilksongTweaks.Ui { public static class Conflicts { private static readonly string[] Known = new string[3] { "ReBack.dll", "com.blueraja.rosaries_never_permanently_lost.dll", "CustomDifficulty" }; public static IReadOnlyList<string> Detect() { List<string> list = new List<string>(); try { string pluginPath = Paths.PluginPath; if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath)) { return list; } string[] known = Known; foreach (string text in known) { string path = Path.Combine(pluginPath, text); if (File.Exists(path) || Directory.Exists(path)) { list.Add(text); continue; } string[] files = Directory.GetFiles(pluginPath, text, SearchOption.AllDirectories); int num = 0; if (num < files.Length) { string path2 = files[num]; list.Add(Path.GetFileName(path2)); } } } catch { } return list; } } public sealed class PanelInput { private const float DeadZone = 0.5f; private const float FirstRepeatDelay = 0.35f; private const float RepeatInterval = 0.09f; private static bool _axesUnavailable; private float _verticalHeldFor; private float _horizontalHeldFor; private int _lastVertical; private int _lastHorizontal; public int Vertical { get; private set; } public int Horizontal { get; private set; } public bool Activate { get; private set; } public bool Cancel { get; private set; } public void Sample(float unscaledDelta) { Vertical = Step(RawVertical(), ref _lastVertical, ref _verticalHeldFor, unscaledDelta); Horizontal = Step(RawHorizontal(), ref _lastHorizontal, ref _horizontalHeldFor, unscaledDelta); Activate = Input.GetKeyDown((KeyCode)330) || Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)32); Cancel = Input.GetKeyDown((KeyCode)331) || Input.GetKeyDown((KeyCode)27); } private static int RawVertical() { if (Input.GetKey((KeyCode)273)) { return -1; } if (Input.GetKey((KeyCode)274)) { return 1; } float num = SafeAxis("Vertical"); if (num > 0.5f) { return -1; } if (num < -0.5f) { return 1; } return 0; } private static int RawHorizontal() { if (Input.GetKey((KeyCode)276)) { return -1; } if (Input.GetKey((KeyCode)275)) { return 1; } float num = SafeAxis("Horizontal"); if (num < -0.5f) { return -1; } if (num > 0.5f) { return 1; } return 0; } private static float SafeAxis(string name) { if (_axesUnavailable) { return 0f; } try { return Input.GetAxisRaw(name); } catch { _axesUnavailable = true; Plugin.Log.LogWarning((object)("Input axis '" + name + "' is not defined by the game; gamepad sticks disabled. Keyboard navigation still works.")); return 0f; } } private static int Step(int raw, ref int last, ref float heldFor, float delta) { if (raw == 0) { last = 0; heldFor = 0f; return 0; } if (raw != last) { last = raw; heldFor = 0f; return raw; } heldFor += delta; if (heldFor < 0.35f) { return 0; } heldFor -= 0.09f; return raw; } } public sealed class Theme { public static readonly Color Silk = new Color(0.93f, 0.91f, 0.86f); public static readonly Color Muted = new Color(0.62f, 0.6f, 0.58f); public static readonly Color Accent = new Color(0.85f, 0.44f, 0.52f); public static readonly Color Good = new Color(0.55f, 0.8f, 0.55f); public static readonly Color Warn = new Color(0.92f, 0.74f, 0.35f); public static readonly Color Bad = new Color(0.88f, 0.45f, 0.45f); public static readonly Color Panel = new Color(0.09f, 0.09f, 0.12f, 0.96f); private bool _built; public GUIStyle Window { get; private set; } public GUIStyle SectionTitle { get; private set; } public GUIStyle SectionDesc { get; private set; } public GUIStyle RowLabel { get; private set; } public GUIStyle Value { get; private set; } public GUIStyle Badge { get; private set; } public GUIStyle Footer { get; private set; } public GUIStyle Toast { get; private set; } public GUIStyle SelectedRow { get; private set; } public void EnsureBuilt() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0038: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: 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_00d7: Expected O, but got Unknown //IL_00dc: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Expected O, but got Unknown //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Expected O, but got Unknown //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Expected O, but got Unknown //IL_023e: Expected O, but got Unknown //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Expected O, but got Unknown //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Expected O, but got Unknown //IL_02ac: Expected O, but got Unknown //IL_02da: Unknown result type (might be due to invalid IL or missing references) if (!_built) { _built = true; Window = new GUIStyle(GUI.skin.window) { padding = new RectOffset(14, 14, 24, 12) }; Window.normal.background = SolidTexture(Panel); Window.onNormal.background = Window.normal.background; Window.normal.textColor = Silk; Window.onNormal.textColor = Silk; Window.fontStyle = (FontStyle)1; SectionTitle = new GUIStyle(GUI.skin.label) { fontStyle = (FontStyle)1, fontSize = 14, margin = new RectOffset(0, 0, 10, 2) }; SectionTitle.normal.textColor = Accent; SectionDesc = new GUIStyle(GUI.skin.label) { fontSize = 11, wordWrap = true }; SectionDesc.normal.textColor = Muted; RowLabel = new GUIStyle(GUI.skin.label) { fontSize = 12 }; RowLabel.normal.textColor = Silk; Value = new GUIStyle(GUI.skin.label) { fontSize = 12, alignment = (TextAnchor)5, fontStyle = (FontStyle)1 }; Value.normal.textColor = Silk; Badge = new GUIStyle(GUI.skin.label) { fontSize = 10, alignment = (TextAnchor)5, fontStyle = (FontStyle)1 }; Footer = new GUIStyle(GUI.skin.label) { fontSize = 10, wordWrap = true }; Footer.normal.textColor = Muted; Toast = new GUIStyle(GUI.skin.box) { fontSize = 15, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, padding = new RectOffset(18, 18, 12, 12) }; Toast.normal.background = SolidTexture(new Color(0.06f, 0.06f, 0.08f, 0.88f)); Toast.normal.textColor = Silk; SelectedRow = new GUIStyle { padding = new RectOffset(6, 6, 1, 1), margin = new RectOffset(-6, -6, 0, 0) }; SelectedRow.normal.background = SolidTexture(new Color(Accent.r, Accent.g, Accent.b, 0.22f)); } } private static Texture2D SolidTexture(Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, color); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } } public sealed class TweakWindow { private struct NavRow { public ITweakModule Module; public ISettingRow Row; } private const int WindowId = 5327699; private static readonly Vector2 Size = new Vector2(430f, 560f); private const int FloatSliderSteps = 20; private readonly ModuleRegistry _registry; private readonly Theme _theme = new Theme(); private readonly PanelInput _input = new PanelInput(); private readonly Dictionary<string, bool> _listening = new Dictionary<string, bool>(); private readonly List<NavRow> _nav = new List<NavRow>(); private IReadOnlyList<string> _conflicts; private Rect _rect = new Rect(60f, 60f, Size.x, Size.y); private Vector2 _scroll; private bool _showCredits; private int _focus; private int _drawIndex; public bool Visible { get; set; } public TweakWindow(ModuleRegistry registry) { //IL_004b: 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) _registry = registry; } public void HandleInput(float unscaledDelta) { if (!Visible) { return; } RebuildNav(); if (_nav.Count == 0) { return; } _input.Sample(unscaledDelta); if (_input.Cancel) { Visible = false; return; } if (_input.Vertical != 0) { _focus = (_focus + _input.Vertical + _nav.Count) % _nav.Count; } if (_input.Horizontal != 0) { Adjust(_nav[_focus], _input.Horizontal); } if (_input.Activate) { Activate(_nav[_focus]); } } private void RebuildNav() { _nav.Clear(); foreach (ITweakModule module in _registry.Modules) { if (module.Status.State == TweakState.Unavailable) { continue; } foreach (ISettingRow setting in module.Settings) { _nav.Add(new NavRow { Module = module, Row = setting }); } } if (_focus >= _nav.Count) { _focus = 0; } } private void Adjust(NavRow nav, int direction) { if (nav.Row is BoolRow boolRow) { boolRow.Entry.Value = direction > 0; } else if (nav.Row is IntRow intRow) { intRow.Entry.Value = Mathf.Clamp(intRow.Entry.Value + direction, intRow.Min, intRow.Max); } else if (nav.Row is FloatRow floatRow) { float num = (floatRow.Max - floatRow.Min) / 20f; floatRow.Entry.Value = Mathf.Clamp(floatRow.Entry.Value + (float)direction * num, floatRow.Min, floatRow.Max); } } private void Activate(NavRow nav) { if (nav.Row is BoolRow boolRow) { boolRow.Entry.Value = !boolRow.Entry.Value; } else if (nav.Row is KeyRow keyRow) { string key = nav.Module.Id + ":" + keyRow.Label; _listening[key] = !Listening(key); } } private bool Listening(string key) { bool value; return _listening.TryGetValue(key, out value) && value; } public void Draw() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) _theme.EnsureBuilt(); DrawCountdownToast(); if (Visible) { if (_conflicts == null) { _conflicts = Conflicts.Detect(); } _rect = GUILayout.Window(5327699, _rect, new WindowFunction(DrawBody), $" SILKSONG TWEAKS · {_registry.ActiveCount}/{_registry.Modules.Count} active", _theme.Window, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(Size.x), GUILayout.Height(Size.y) }); } } private void DrawCountdownToast() { //IL_0066: 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) //IL_0091: Expected O, but got Unknown //IL_008c: 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_0098: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) ReturnToDeathModule returnToDeathModule = null; foreach (ITweakModule module in _registry.Modules) { returnToDeathModule = module as ReturnToDeathModule; if (returnToDeathModule != null) { break; } } if (returnToDeathModule != null && !(returnToDeathModule.CountdownRemaining <= 0f)) { string text = $"Returning to where you died in {returnToDeathModule.CountdownRemaining:0.0}s" + $"\nPress {returnToDeathModule.CancelKey} to stay here"; Vector2 val = _theme.Toast.CalcSize(new GUIContent(text)); GUI.Label(new Rect(((float)Screen.width - val.x) * 0.5f, (float)Screen.height * 0.14f, val.x, val.y), text, _theme.Toast); } } private void DrawBody(int id) { //IL_001e: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (_conflicts != null && _conflicts.Count > 0) { DrawConflictWarning(); } _scroll = GUILayout.BeginScrollView(_scroll, Array.Empty<GUILayoutOption>()); _drawIndex = 0; foreach (ITweakModule module in _registry.Modules) { DrawModule(module); } GUILayout.Space(6f); GUILayout.Label("Move: D-pad / left stick / arrows Change: left-right Toggle: A / Enter\nClose: B / Esc / the open button", _theme.Footer, Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); DrawCredits(); GUILayout.EndScrollView(); GUI.DragWindow(new Rect(0f, 0f, Size.x, 22f)); } private void DrawConflictWarning() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) Color textColor = _theme.SectionDesc.normal.textColor; _theme.SectionDesc.normal.textColor = Theme.Bad; GUILayout.Label("CONFLICT: these mods do the same job and will fight this one.\n · " + string.Join("\n · ", ToArray(_conflicts)) + "\nRemove them from BepInEx/plugins.", _theme.SectionDesc, Array.Empty<GUILayoutOption>()); _theme.SectionDesc.normal.textColor = textColor; GUILayout.Space(6f); } private void DrawModule(ITweakModule module) { GUILayout.Label(module.DisplayName, _theme.SectionTitle, Array.Empty<GUILayoutOption>()); Widgets.StatusBadge(_theme, module.Status, module.LastFiredUtcTicks); if (module.Status.State == TweakState.Unavailable) { GUILayout.Label(module.Description, _theme.SectionDesc, Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); return; } foreach (ISettingRow setting in module.Settings) { bool selected = _drawIndex == _focus; _drawIndex++; DrawRow(module, setting, selected); } GUILayout.Space(6f); } private void DrawRow(ITweakModule module, ISettingRow row, bool selected) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) if (selected) { GUILayout.BeginHorizontal(_theme.SelectedRow, Array.Empty<GUILayoutOption>()); } if (row is BoolRow boolRow) { bool value = boolRow.Entry.Value; Widgets.Toggle(_theme, boolRow.Label, boolRow.Tooltip, ref value); if (value != boolRow.Entry.Value) { boolRow.Entry.Value = value; } } else if (row is IntRow intRow) { int value2 = intRow.Entry.Value; Widgets.IntSlider(_theme, intRow.Label, intRow.Tooltip, ref value2, intRow.Min, intRow.Max); if (value2 != intRow.Entry.Value) { intRow.Entry.Value = value2; } } else if (row is FloatRow floatRow) { float value3 = floatRow.Entry.Value; Widgets.FloatSlider(_theme, floatRow.Label, floatRow.Tooltip, ref value3, new FloatRange(floatRow.Min, floatRow.Max, floatRow.Format)); if (!Mathf.Approximately(value3, floatRow.Entry.Value)) { floatRow.Entry.Value = value3; } } else if (row is KeyRow keyRow) { string key = module.Id + ":" + keyRow.Label; bool listening = Listening(key); KeyCode value4 = keyRow.Entry.Value; Widgets.KeyBinder(_theme, keyRow.Label, keyRow.Tooltip, ref value4, ref listening); _listening[key] = listening; if (value4 != keyRow.Entry.Value) { keyRow.Entry.Value = value4; } } if (selected) { GUILayout.EndHorizontal(); } } private void DrawCredits() { _showCredits = GUILayout.Toggle(_showCredits, _showCredits ? "Credits" : "Credits ...", Array.Empty<GUILayoutOption>()); if (_showCredits) { GUILayout.Label("Silksong Tweaks is an independent implementation, built against the game's own API. It replaces three mods whose ideas it owes a debt to — with thanks to:\n - Xiaohai (XiaohaiMod) - ReBack, for returning to the death location\n - BlueRaja - rosaries never permanently lost, for cocoon merging\n - Ericky1694 - CustomDifficulty, for health and damage tuning\n\nMIT licensed. Built on BepInEx and HarmonyX.", _theme.Footer, Array.Empty<GUILayoutOption>()); } } private static string[] ToArray(IReadOnlyList<string> items) { string[] array = new string[items.Count]; for (int i = 0; i < items.Count; i++) { array[i] = items[i]; } return array; } } public static class Widgets { private const float LabelWidth = 190f; private const float ValueWidth = 60f; private const float RowHeight = 22f; public static void Toggle(Theme theme, string label, string tooltip, ref bool value) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); GUILayout.FlexibleSpace(); value = GUILayout.Toggle(value, value ? " ON" : " OFF", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUILayout.EndHorizontal(); } public static void IntSlider(Theme theme, string label, string tooltip, ref int value, int min, int max) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); value = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)value, (float)min, (float)max, Array.Empty<GUILayoutOption>())); GUILayout.Label(value.ToString(), theme.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUILayout.EndHorizontal(); } public static void FloatSlider(Theme theme, string label, string tooltip, ref float value, FloatRange range) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); value = GUILayout.HorizontalSlider(value, range.Min, range.Max, Array.Empty<GUILayoutOption>()); GUILayout.Label(Format(value, range.Format), theme.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUILayout.EndHorizontal(); } public static void KeyBinder(Theme theme, string label, string tooltip, ref KeyCode value, ref bool listening) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected I4, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); GUILayout.FlexibleSpace(); if (GUILayout.Button(listening ? "press a key..." : ((object)Unsafe.As<KeyCode, KeyCode>(ref value)/*cast due to .constrained prefix*/).ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { listening = !listening; } if (listening && Event.current != null && Event.current.isKey && (int)Event.current.keyCode != 0) { value = (KeyCode)(int)Event.current.keyCode; listening = false; Event.current.Use(); } GUILayout.EndHorizontal(); } public static void StatusBadge(Theme theme, TweakStatus status, long lastFiredTicks) { //IL_0047: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: 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_0065: 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_0032: 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) string text; Color textColor; switch (status.State) { case TweakState.Active: text = ((lastFiredTicks > 0) ? ("ACTIVE · fired " + Ago(lastFiredTicks)) : "ACTIVE · not yet fired"); textColor = ((lastFiredTicks > 0) ? Theme.Good : Theme.Warn); break; case TweakState.Disabled: text = "OFF"; textColor = Theme.Muted; break; default: text = "UNAVAILABLE · " + status.Reason; textColor = Theme.Bad; break; } Color textColor2 = theme.Badge.normal.textColor; theme.Badge.normal.textColor = textColor; GUILayout.Label(text, theme.Badge, Array.Empty<GUILayoutOption>()); theme.Badge.normal.textColor = textColor2; } private static string Format(float value, string format) { if (format == "0%") { return Mathf.RoundToInt(value * 100f) + "%"; } if (format == "0.0s") { return value.ToString("0.0") + "s"; } return value.ToString("0.00") + "x"; } private static string Ago(long utcTicks) { double totalSeconds = (DateTime.UtcNow - new DateTime(utcTicks, DateTimeKind.Utc)).TotalSeconds; if (totalSeconds < 2.0) { return "just now"; } if (totalSeconds < 60.0) { return (int)totalSeconds + "s ago"; } if (totalSeconds < 3600.0) { return (int)(totalSeconds / 60.0) + "m ago"; } return (int)(totalSeconds / 3600.0) + "h ago"; } } public struct FloatRange { public float Min { get; } public float Max { get; } public string Format { get; } public FloatRange(float min, float max, string format) { Min = min; Max = max; Format = format; } } } namespace SilksongTweaks.Rules { public static class DamageRules { public static int Scale(int incoming, float multiplier) { if (incoming <= 0) { return incoming; } if (multiplier <= 0f) { return 0; } int num = (int)Math.Round((double)incoming * (double)multiplier); if (num < 1) { num = 1; } return num; } } public static class HealthRules { public const int MinMasks = 1; public const int MaxMasks = 20; public static int Resolve(int vanillaMax, int configuredMasks, bool enabled) { if (!enabled) { return vanillaMax; } int num = Clamp(configuredMasks, 1, 20); if (vanillaMax <= num) { return num; } return vanillaMax; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } public static class CocoonRules { public static int Merge(int previousPool, int carriedThisDeath, float keepFraction) { if (previousPool < 0) { previousPool = 0; } if (carriedThisDeath < 0) { carriedThisDeath = 0; } if (keepFraction <= 0f) { return carriedThisDeath; } if (keepFraction > 1f) { keepFraction = 1f; } long num = (long)(int)((double)previousPool * (double)keepFraction) + (long)carriedThisDeath; if (num <= int.MaxValue) { return (int)num; } return int.MaxValue; } } } namespace SilksongTweaks.Modules { public sealed class DamageTakenModule : ModuleBase { private const float MinMultiplier = 0f; private const float MaxMultiplier = 3f; private static DamageTakenModule _instance; private ConfigEntry<float> _multiplier; public override string Id => "DamageTaken"; public override string DisplayName => "Damage taken"; public override string Description => "Scale how much damage Hornet takes."; protected override void BindSettings(ConfigFile config) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown _multiplier = config.Bind<float>(Id, "Multiplier", 1f, new ConfigDescription("1 = vanilla. 0.5 = half damage. 0 = invulnerable. Above 1 makes the game harder.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); AddRow(new FloatRow("Multiplier", "1 = vanilla, 0 = invulnerable.", _multiplier, 0f, 3f, "0.00x")); } protected override TweakStatus Apply(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.TakeDamage, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, new HarmonyMethod(typeof(DamageTakenModule), "TakeDamagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return TweakStatus.Active; } private static void TakeDamagePrefix(ref int damageAmount) { DamageTakenModule instance = _instance; if (instance != null && instance.IsOn && damageAmount > 0) { int num = DamageRules.Scale(damageAmount, instance._multiplier.Value); if (num != damageAmount) { damageAmount = num; instance.MarkFired(); } } } } public sealed class KeepRosariesModule : ModuleBase { private static KeepRosariesModule _instance; private ConfigEntry<float> _keepFraction; private int _poolBeforeDeath; private bool _armed; public override string Id => "KeepRosaries"; public override string DisplayName => "Keep rosaries"; public override string Description => "Dying twice no longer destroys your first cocoon."; protected override void BindSettings(ConfigFile config) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown _keepFraction = config.Bind<float>(Id, "KeepFraction", 1f, new ConfigDescription("Fraction of the previous cocoon carried into the new one. 1 = lose nothing, 0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); AddRow(new FloatRow("Keep from old cocoon", "1 = lose nothing, 0 = vanilla behaviour.", _keepFraction, 0f, 1f, "0%")); } protected override TweakStatus Apply(Harmony harmony) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.Die, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } string error2; MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.HeroRespawned, out error2); if (methodBase2 == null) { return TweakStatus.Unavailable(error2); } harmony.Patch(methodBase, new HarmonyMethod(typeof(KeepRosariesModule), "DiePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(KeepRosariesModule), "HeroRespawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return TweakStatus.Active; } private static void DiePrefix() { KeepRosariesModule instance = _instance; if (instance != null && instance.IsOn) { PlayerData instance2 = PlayerData.instance; if (instance2 != null) { instance._poolBeforeDeath = instance2.HeroCorpseMoneyPool; instance._armed = true; } } } private static void HeroRespawnedPostfix() { KeepRosariesModule instance = _instance; if (instance == null || !instance.IsOn || !instance._armed) { return; } instance._armed = false; PlayerData instance2 = PlayerData.instance; if (instance2 != null) { int num = CocoonRules.Merge(instance._poolBeforeDeath, instance2.HeroCorpseMoneyPool, instance._keepFraction.Value); if (num != instance2.HeroCorpseMoneyPool) { Plugin.Log.LogInfo((object)($"[KeepRosaries] cocoon {instance2.HeroCorpseMoneyPool} -> {num} " + $"(rescued {num - instance2.HeroCorpseMoneyPool} from the previous cocoon)")); instance2.HeroCorpseMoneyPool = num; instance.MarkFired(); } } } } public sealed class MaxHealthModule : ModuleBase { private const string MaxHealthKey = "maxHealth"; private const string MaxHealthField = "maxHealth"; private static MaxHealthModule _instance; private static int _rewritten; private ConfigEntry<int> _masks; public override string Id => "MaxHealth"; public override string DisplayName => "Max masks"; public override string Description => "Raise Hornet's maximum health."; protected override void BindSettings(ConfigFile config) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown _masks = config.Bind<int>(Id, "Masks", 8, new ConfigDescription("Maximum masks. Never lowers you below what you have earned in-game.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>())); AddRow(new IntRow("Masks", "How many masks Hornet has at full health.", _masks, 1, 20)); _masks.SettingChanged += delegate { RefreshMaskDisplay(); }; base.EnabledEntry.SettingChanged += delegate { RefreshMaskDisplay(); }; } private static void RefreshMaskDisplay() { try { HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null)) { MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "MaxHealth", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"[MaxHealth] HeroController.MaxHealth() not found; HUD will refresh on its own next bench."); } else { methodInfo.Invoke(instance, null); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[MaxHealth] HUD refresh failed: " + ex.Message)); } } protected override TweakStatus Apply(Harmony harmony) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.CurrentMaxHealth, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.GetInt, out error); if (methodBase2 == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, (HarmonyMethod)null, new HarmonyMethod(typeof(MaxHealthModule), "CurrentMaxHealthPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(MaxHealthModule), "GetIntPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); TweakStatus tweakStatus = RestoreInvariantIn(harmony); if (tweakStatus != null) { return tweakStatus; } if (_rewritten == 0) { return TweakStatus.Unavailable("no maxHealth field reads found to rewrite — the game's health code changed"); } base.Log.LogInfo((object)$"[MaxHealth] restored max-health invariant at {_rewritten} site(s)"); return TweakStatus.Active; } private TweakStatus RestoreInvariantIn(Harmony harmony) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown HookTarget[] obj = new HookTarget[3] { HookTargets.PlayerDataAddHealth, HookTargets.PlayerDataTakeHealth, HookTargets.PlayerDataMaxHealth }; HarmonyMethod val = new HarmonyMethod(typeof(MaxHealthModule), "MaxHealthTranspiler", (Type[])null); HookTarget[] array = obj; for (int i = 0; i < array.Length; i++) { string error; MethodBase methodBase = ModuleBase.Resolve(array[i], out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); } return null; } private static IEnumerable<CodeInstruction> MaxHealthTranspiler(IEnumerable<CodeInstruction> instructions) { MethodInfo getter = AccessTools.PropertyGetter(typeof(PlayerData), "CurrentMaxHealth"); foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo { Name: "maxHealth" } fieldInfo && fieldInfo.DeclaringType == typeof(PlayerData)) { instruction.opcode = OpCodes.Call; instruction.operand = getter; _rewritten++; } yield return instruction; } } private static void CurrentMaxHealthPostfix(ref int __result) { MaxHealthModule instance = _instance; if (instance != null && instance.IsOn) { int num = HealthRules.Resolve(__result, instance._masks.Value, enabled: true); if (num != __result) { __result = num; instance.MarkFired(); } } } private static void GetIntPostfix(string intName, ref int __result) { MaxHealthModule instance = _instance; if (instance != null && instance.IsOn && !(intName != "maxHealth")) { __result = HealthRules.Resolve(__result, instance._masks.Value, enabled: true); } } } public abstract class ModuleBase : ITweakModule { private readonly List<ISettingRow> _rows = new List<ISettingRow>(); private TweakStatus _hookStatus = TweakStatus.Unavailable("not applied yet"); protected ConfigEntry<bool> EnabledEntry { get; private set; } protected ManualLogSource Log => Plugin.Log; public abstract string Id { get; } public abstract string DisplayName { get; } public abstract string Description { get; } public TweakStatus Status { get { if (_hookStatus.State == TweakState.Unavailable) { return _hookStatus; } if (EnabledEntry == null || EnabledEntry.Value) { return TweakStatus.Active; } return TweakStatus.Disabled; } protected set { _hookStatus = value; } } public long LastFiredUtcTicks { get; private set; } public IReadOnlyList<ISettingRow> Settings => _rows; public bool IsOn { get { if (Status.State != TweakState.Unavailable && EnabledEntry != null) { return EnabledEntry.Value; } return false; } } public void BindConfig(ConfigFile config) { EnabledEntry = config.Bind<bool>(Id, "Enabled", true, Description); AddRow(new BoolRow("Enabled", Description, EnabledEntry)); BindSettings(config); } protected abstract void BindSettings(ConfigFile config); protected void AddRow(ISettingRow row) { _rows.Add(row); } public TweakStatus TryApply(Harmony harmony) { try { Status = Apply(harmony); } catch (Exception ex) { Status = TweakStatus.Unavailable(ex.Message); Log.LogError((object)$"[{Id}] failed to apply: {ex}"); } return Status; } protected abstract TweakStatus Apply(Harmony harmony); public void MarkFired() { LastFiredUtcTicks = DateTime.UtcNow.Ticks; } protected static MethodBase Resolve(HookTarget target, out string error) { Type type = AccessTools.TypeByName(target.DeclaringType); if (type == null) { error = "type '" + target.DeclaringType + "' not found"; return null; } MethodBase methodBase = ((target.Kind == HookKind.PropertyGetter) ? AccessTools.PropertyGetter(type, target.Member) : AccessTools.Method(type, target.Member, (Type[])null, (Type[])null)); if (methodBase == null) { error = target.DeclaringType + "." + target.Member + " not found"; return null; } error = null; return methodBase; } } public sealed class ReturnToDeathModule : ModuleBase { private const float MinCountdown = 0f; private const float MaxCountdown = 15f; private const float ArrivalTimeout = 15f; private const float SettleSeconds = 0.35f; private static ReturnToDeathModule _instance; private ConfigEntry<float> _countdown; private ConfigEntry<KeyCode> _cancelKey; private string _deathScene; private string _deathGate; private Vector3 _deathPosition; private bool _hasRecord; private bool _returning; public override string Id => "ReturnToDeath"; public override string DisplayName => "Return to death spot"; public override string Description => "Travel back to where you died after respawning."; public float CountdownRemaining { get; private set; } public KeyCode CancelKey { get { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (_cancelKey == null) { return (KeyCode)110; } return _cancelKey.Value; } } protected override void BindSettings(ConfigFile config) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown _countdown = config.Bind<float>(Id, "CountdownSeconds", 5f, new ConfigDescription("Seconds before the return happens, giving you time to cancel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 15f), Array.Empty<object>())); _cancelKey = config.Bind<KeyCode>(Id, "CancelKey", (KeyCode)110, "Press during the countdown to stay at the bench."); AddRow(new FloatRow("Countdown", "Seconds before returning.", _countdown, 0f, 15f, "0.0s")); AddRow(new KeyRow("Cancel key", "Press during the countdown to stay put.", _cancelKey)); } protected override TweakStatus Apply(Harmony harmony) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.Die, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.HeroRespawned, out error); if (methodBase2 == null) { return TweakStatus.Unavailable(error); } if (ModuleBase.Resolve(HookTargets.BeginSceneTransition, out error) == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, new HarmonyMethod(typeof(ReturnToDeathModule), "DiePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(ReturnToDeathModule), "HeroRespawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return TweakStatus.Active; } private static void DiePrefix() { //IL_0044: 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_00a6: Unknown result type (might be due to invalid IL or missing references) ReturnToDeathModule instance = _instance; if (instance == null || !instance.IsOn) { return; } HeroController instance2 = HeroController.instance; GameManager instance3 = GameManager.instance; if (!((Object)(object)instance2 == (Object)null) && !((Object)(object)instance3 == (Object)null)) { instance._deathScene = instance3.GetSceneNameString(); instance._deathPosition = ((Component)instance2).transform.position; instance._deathGate = FindGateInCurrentScene(); instance._hasRecord = !string.IsNullOrEmpty(instance._deathGate); if (!instance._hasRecord) { Plugin.Log.LogWarning((object)("[ReturnToDeath] no transition point found in '" + instance._deathScene + "'. Staying at the bench rather than risking a transition with no entry gate.")); } else { Plugin.Log.LogInfo((object)($"[ReturnToDeath] recorded death in '{instance._deathScene}' at {instance._deathPosition} " + "via gate '" + instance._deathGate + "'")); } } } private static string FindGateInCurrentScene() { List<TransitionPoint> transitionPoints = TransitionPoint.TransitionPoints; if (transitionPoints == null || transitionPoints.Count == 0) { return null; } string text = null; foreach (TransitionPoint item in transitionPoints) { if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(((Object)item).name)) { if (!item.isADoor) { return ((Object)item).name; } if (text == null) { text = ((Object)item).name; } } } return text; } private static void HeroRespawnedPostfix() { ReturnToDeathModule instance = _instance; if (instance != null && instance.IsOn && instance._hasRecord && !instance._returning) { Plugin.Instance.Run(instance.ReturnRoutine()); } } private IEnumerator ReturnRoutine() { _returning = true; CountdownRemaining = _countdown.Value; while (CountdownRemaining > 0f) { if (Input.GetKeyDown(CancelKey)) { Plugin.Log.LogInfo((object)"[ReturnToDeath] cancelled by player"); Finish(); yield break; } CountdownRemaining -= Time.unscaledDeltaTime; yield return null; } CountdownRemaining = 0f; yield return Travel(); Finish(); } private IEnumerator Travel() { GameManager gm = GameManager.instance; if ((Object)(object)gm == (Object)null || string.IsNullOrEmpty(_deathScene) || string.IsNullOrEmpty(_deathGate)) { yield break; } yield return WaitUntilRespawnFinished(); if (gm.GetSceneNameString() != _deathScene) { Plugin.Log.LogInfo((object)("[ReturnToDeath] entering '" + _deathScene + "' via gate '" + _deathGate + "'")); gm.BeginSceneTransition(new SceneLoadInfo { SceneName = _deathScene, EntryGateName = _deathGate }); float waited = 0f; while (waited < 15f) { GameManager instance = GameManager.instance; if ((Object)(object)instance != (Object)null && instance.GetSceneNameString() == _deathScene && (Object)(object)HeroController.instance != (Object)null) { break; } waited += Time.unscaledDeltaTime; yield return null; } if (waited >= 15f) { Plugin.Log.LogWarning((object)"[ReturnToDeath] timed out waiting for the scene; leaving you where you are"); yield break; } yield return (object)new WaitForSeconds(0.35f); } PlaceHeroAtDeathSpot(); MarkFired(); } private static IEnumerator WaitUntilRespawnFinished() { float waited = 0f; while (waited < 15f) { GameManager instance = GameManager.instance; HeroController instance2 = HeroController.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && !instance.RespawningHero) { break; } waited += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSeconds(0.35f); } private void PlaceHeroAtDeathSpot() { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_008e: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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) HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "TryFindGroundPoint", (Type[])null, (Type[])null); if (methodInfo != null) { object[] array = new object[3] { (object)default(Vector2), Vector2.op_Implicit(_deathPosition), true }; object obj = methodInfo.Invoke(instance, array); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { Vector2 val = (Vector2)array[0]; ((Component)instance).transform.position = new Vector3(val.x, val.y, ((Component)instance).transform.position.z); Plugin.Log.LogInfo((object)$"[ReturnToDeath] placed at ground point {val}"); return; } } ((Component)instance).transform.position = _deathPosition; Plugin.Log.LogInfo((object)$"[ReturnToDeath] placed at recorded position {_deathPosition}"); } private void Finish() { CountdownRemaining = 0f; _returning = false; _hasRecord = false; } } }