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.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Pigeon; using Pigeon.Movement; using Pigeon.UI; using Sparroh.UI; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.UI; [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("Sparroh")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.1.5.0")] [assembly: AssemblyInformationalVersion("2.1.5")] [assembly: AssemblyProduct("BacklogImprovements")] [assembly: AssemblyTitle("BacklogImprovements")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.1.5.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public static class BacklogUI { public sealed class WindowState { public readonly List RerollOverlays = new List(); public UIButton CancelButton; public UIButton ClearButton; public UIButton EditButton; public UIButton PageButton; public bool RerollSelecting; public GameObject Root; public UIButton SingleButton; public DirectiveWindow Window; } internal const float MainButtonMinWidth = 120f; internal const float ButtonHeight = 32f; private const float BarLeftAnchor = 0.12f; private const float CompactFontSize = 13f; private const float BarWidth = 720f; private const float BarHeight = 40f; private const float BarBottomPad = 18f; private const float ButtonGap = 4f; private const float LabelPad = 8f; private const float TextWidthPad = 24f; private static readonly FieldInfo DirectivesField = AccessTools.Field(typeof(DirectiveWindow), "directives"); private static readonly FieldInfo IsAnimatingField = AccessTools.Field(typeof(DirectiveWindow), "isAnimatingPage"); private static readonly FieldInfo DirectiveField = AccessTools.Field(typeof(DirectiveButton), "directive"); private static readonly Dictionary States = new Dictionary(); public static bool IsPathEditing => PathLogic.IsEditing; public static bool IsRerollSelecting { get { foreach (KeyValuePair state in States) { if (state.Value.RerollSelecting) { return true; } } return false; } } public static bool IsAnySelectionMode { get { if (!IsPathEditing) { return IsRerollSelecting; } return true; } } public static void OnWindowOpened(DirectiveWindow window) { if ((Object)(object)window == (Object)null) { return; } FreePages.ApplyFreeNextPage(window); bool flag = ConfigManager.EnablePreselect?.Value ?? true; bool flag2 = ConfigManager.EnableReroll?.Value ?? true; if (!flag && !flag2) { return; } int instanceID = ((Object)window).GetInstanceID(); if (States.TryGetValue(instanceID, out var value) && (Object)(object)value.Root != (Object)null) { value.Window = window; PathToolbar.ExitEdit(value, activateNext: false, refreshWindow: false); RerollToolbar.ExitSelection(value); RefreshAll(value); if (flag) { PathVisualizer.Refresh(window); PathLogic.TryAutoProgress(window); } return; } WindowState windowState = BuildUi(window, flag, flag2); if (windowState == null) { if (flag) { PathLogic.TryAutoProgress(window); } return; } States[instanceID] = windowState; RefreshAll(windowState); if (flag) { PathVisualizer.Refresh(window); PathLogic.TryAutoProgress(window); } } public static void OnWindowClosed(DirectiveWindow window) { if ((Object)(object)window == (Object)null) { return; } int instanceID = ((Object)window).GetInstanceID(); if (States.TryGetValue(instanceID, out var value)) { if (PathLogic.IsEditing) { PathLogic.SetEditing(editing: false); } PathVisualizer.Clear(window); DestroyState(value); States.Remove(instanceID); } } public static void OnDirectivesRefreshed(DirectiveWindow window) { if ((Object)(object)window == (Object)null) { return; } FreePages.ApplyFreeNextPage(window); bool num = ConfigManager.EnablePreselect?.Value ?? true; WindowState value; bool flag = States.TryGetValue(((Object)window).GetInstanceID(), out value); if (flag) { value.Window = window; RefreshAll(value); } if (num && !PathLogic.IsEditing) { if (flag) { PathVisualizer.Refresh(window); } PathLogic.TryAutoProgress(window); } if (flag && value.RerollSelecting) { RerollToolbar.RebuildOverlays(value); } } public static void CleanupAll() { PathLogic.SetEditing(editing: false); foreach (KeyValuePair state in States) { DestroyState(state.Value); } States.Clear(); PathVisualizer.CleanupAll(); } public static bool TryHandleDirectiveClick(DirectiveButton button) { if ((Object)(object)button == (Object)null) { return false; } if (PathLogic.IsEditing) { return PathToolbar.TryHandleClick(button, FindStateForButton(button)); } if (IsRerollSelecting) { return RerollToolbar.TryHandleClick(button, FindStateForButton(button)); } return false; } internal static void RefreshAll(WindowState state) { if (state != null) { PathToolbar.RefreshLabels(state); RerollToolbar.RefreshLabels(state); } } internal static DirectiveInstance GetDirective(DirectiveButton button) { object? obj = DirectiveField?.GetValue(button); return (DirectiveInstance)((obj is DirectiveInstance) ? obj : null); } internal static DirectiveButton[] GetDirectiveButtons(DirectiveWindow window) { return DirectivesField?.GetValue(window) as DirectiveButton[]; } internal static bool IsAnimating(DirectiveWindow window) { if ((Object)(object)window == (Object)null || IsAnimatingField == null) { return false; } try { return (bool)IsAnimatingField.GetValue(window); } catch { return false; } } internal static void ApplyCompactLabel(UIButton button) { if (!((Object)(object)((button != null) ? button.Label : null) == (Object)null)) { TextMeshProUGUI label = button.Label; ((TMP_Text)label).enableAutoSizing = false; ((TMP_Text)label).fontSize = 13f; ((TMP_Text)label).fontSizeMin = 13f; ((TMP_Text)label).fontSizeMax = 13f; ((TMP_Text)label).enableWordWrapping = false; ((TMP_Text)label).overflowMode = (TextOverflowModes)0; UIHelpers.SetFillParent(((TMP_Text)label).rectTransform, 8f); } } internal static void FitButtonWidth(UIButton button, float minWidth) { if (button == null) { return; } ApplyCompactLabel(button); float num = minWidth; if ((Object)(object)button.Label != (Object)null) { ((TMP_Text)button.Label).ForceMeshUpdate(false, false); float num2 = ((TMP_Text)button.Label).preferredWidth + 24f; if (num2 > num) { num = num2; } } button.SetWidth(num); } internal static void PlayRerollSound() { try { if ((Object)(object)PlayerLook.Instance != (Object)null) { AkUnitySoundEngine.PostEvent("UI_Directive_Page_Complete2", ((Component)PlayerLook.Instance).gameObject); } } catch { } } public static void PlayPathSound() { try { if ((Object)(object)PlayerLook.Instance != (Object)null) { AkUnitySoundEngine.PostEvent("UI_Directive_Connect", ((Component)PlayerLook.Instance).gameObject); } } catch { } } private static WindowState FindStateForButton(DirectiveButton button) { DirectiveWindow componentInParent = ((Component)button).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && States.TryGetValue(((Object)componentInParent).GetInstanceID(), out var value)) { return value; } using (Dictionary.Enumerator enumerator = States.GetEnumerator()) { if (enumerator.MoveNext()) { return enumerator.Current.Value; } } return null; } private static WindowState BuildUi(DirectiveWindow window, bool preselect, bool reroll) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_00ba: Expected O, but got Unknown try { Transform transform = ((Component)window).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)val == (Object)null) { return null; } UITheme.Initialize(); RectTransform val2 = UIFactory.CreateRect("BacklogImprovementsBar", (Transform)(object)val); val2.anchorMin = new Vector2(0.12f, 0f); val2.anchorMax = new Vector2(0.12f, 0f); val2.pivot = new Vector2(0f, 0f); val2.sizeDelta = new Vector2(720f, 40f); val2.anchoredPosition = new Vector2(0f, 18f); ((Transform)val2).SetAsLastSibling(); UIFactory.AddHorizontalLayout(((Component)val2).gameObject, 4f, new RectOffset(0, 0, 0, 0), (TextAnchor)3, false, false, true, true); WindowState windowState = new WindowState { Window = window, Root = ((Component)val2).gameObject }; if (preselect) { PathToolbar.Build(windowState, val2); } if (reroll) { RerollToolbar.Build(windowState, val2); } return windowState; } catch (Exception arg) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogError((object)$"Failed to build backlog UI: {arg}"); } return null; } } private static void DestroyState(WindowState state) { if (state != null) { RerollToolbar.ClearOverlays(state); UIHelpers.DestroySafe((Object)(object)state.Root); state.Root = null; state.EditButton = null; state.ClearButton = null; state.PageButton = null; state.SingleButton = null; state.CancelButton = null; state.RerollSelecting = false; } } } public static class ConfigManager { private const float DebounceSeconds = 0.25f; private static ConfigFile _config; private static ManualLogSource _logger; private static FileSystemWatcher _configWatcher; private static volatile bool _reloadPending; private static float _lastReloadTime; public static ConfigEntry EnablePreselect { get; private set; } public static ConfigEntry EnableReroll { get; private set; } public static ConfigEntry EnableFreePages { get; private set; } public static ConfigEntry CostPerDirective { get; private set; } public static void Initialize(ConfigFile configFile, ManualLogSource log) { _config = configFile; _logger = log; EnablePreselect = _config.Bind("Features", "Enable Preselect", true, "Enable path preselect, auto-claim, and auto-activate."); EnableReroll = _config.Bind("Features", "Enable Reroll", true, "Enable reroll page / reroll one controls."); EnableFreePages = _config.Bind("Features", "Enable Free Pages", true, "Remove the resource cost for generating the next backlog page. Takes effect immediately (no restart)."); CostPerDirective = _config.Bind("Reroll", "Cost Per Directive", 50, "Gats cost to reroll a single backlog directive. Page reroll costs this amount times the number of eligible directives."); EnableFreePages.SettingChanged += OnFreePagesSettingChanged; try { SetupFileWatcher(); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)("Error setting up config file watcher: " + ex.Message)); } } } public static void Tick() { if (!_reloadPending || Time.unscaledTime - _lastReloadTime < 0.25f) { return; } _reloadPending = false; _lastReloadTime = Time.unscaledTime; try { bool flag = EnableFreePages?.Value ?? false; _config.Reload(); bool flag2 = EnableFreePages?.Value ?? false; ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)$"[FreePages] Config.Reload() → Free={flag2} (was {flag})"); } FreePages.RefreshOpenWindows(); } catch (Exception ex) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogError((object)("Error reloading config: " + ex.Message)); } } } public static void Dispose() { if (EnableFreePages != null) { EnableFreePages.SettingChanged -= OnFreePagesSettingChanged; } if (_configWatcher != null) { _configWatcher.EnableRaisingEvents = false; _configWatcher.Changed -= OnConfigFileChanged; _configWatcher.Created -= OnConfigFileChanged; _configWatcher.Renamed -= OnConfigFileChanged; _configWatcher.Dispose(); _configWatcher = null; } } private static void SetupFileWatcher() { _configWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.backlogimprovements.cfg") { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite), EnableRaisingEvents = true }; _configWatcher.Changed += OnConfigFileChanged; _configWatcher.Created += OnConfigFileChanged; _configWatcher.Renamed += OnConfigFileChanged; } private static void OnConfigFileChanged(object sender, FileSystemEventArgs e) { _reloadPending = true; } private static void OnFreePagesSettingChanged(object sender, EventArgs e) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)$"[FreePages] SettingChanged → Free={EnableFreePages.Value}"); } FreePages.RefreshOpenWindows(); } } [HarmonyPatch(typeof(DirectiveWindow))] internal static class DirectiveWindowPatches { [HarmonyPostfix] [HarmonyPatch("OnOpen")] private static void OnOpenPostfix(DirectiveWindow __instance) { BacklogUI.OnWindowOpened(__instance); } [HarmonyPostfix] [HarmonyPatch("SetupDirectives")] private static void SetupDirectivesPostfix(DirectiveWindow __instance) { BacklogUI.OnDirectivesRefreshed(__instance); } [HarmonyPostfix] [HarmonyPatch("OnDisable")] private static void OnDisablePostfix(DirectiveWindow __instance) { BacklogUI.OnWindowClosed(__instance); } } [HarmonyPatch(typeof(DirectiveWindow), "Update")] internal static class DirectiveWindowUpdatePatch { private static void Postfix(DirectiveWindow __instance) { //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_005f: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_0096: 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) if (BacklogUI.IsAnySelectionMode) { try { if (PlayerInput.Controls != null) { MenuActions menu = PlayerInput.Controls.Menu; if (((MenuActions)(ref menu)).Click.WasPressedThisFrame() && !((Object)(object)EventSystem.current == (Object)null)) { Vector2 position = ((Mouse.current == null) ? Vector2.op_Implicit(Input.mousePosition) : ((InputControl)(object)((Pointer)Mouse.current).position).ReadValue()); PointerEventData val = new PointerEventData(EventSystem.current) { position = position }; List list = new List(); EventSystem.current.RaycastAll(val, list); for (int i = 0; i < list.Count; i++) { RaycastResult val2 = list[i]; DirectiveButton componentInParent = ((RaycastResult)(ref val2)).gameObject.GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { BacklogUI.TryHandleDirectiveClick(componentInParent); break; } } } } return; } catch { return; } } ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if (enablePreselect == null || enablePreselect.Value) { PathLogic.TryAutoProgress(__instance); } } } [HarmonyPatch(typeof(DirectiveButton))] internal static class DirectiveButtonSelectionPatches { [HarmonyPrefix] [HarmonyPatch("HasUnlockAction")] private static bool HasUnlockActionPrefix(DirectiveButton __instance, out UnlockActionParams data, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) data = default(UnlockActionParams); if (!BacklogUI.IsAnySelectionMode) { return true; } __result = false; return false; } [HarmonyPrefix] [HarmonyPatch("GetPrimaryBinding")] private static bool GetPrimaryBindingPrefix(DirectiveButton __instance, out InputAction binding, out string label, ref bool __result) { //IL_0015: 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) if (!BacklogUI.IsAnySelectionMode) { binding = null; label = null; return true; } MenuActions menu = PlayerInput.Controls.Menu; binding = ((MenuActions)(ref menu)).Click; label = (BacklogUI.IsRerollSelecting ? "reroll" : "select"); __result = true; return false; } } public static class FreePages { private static readonly FieldInfo NextPageButtonField = AccessTools.Field(typeof(DirectiveWindow), "nextPageButton"); private static readonly FieldInfo HoldDurationField = AccessTools.Field(typeof(HoverInfoHold), "holdDuration"); private static readonly FieldInfo OnCompleteEventField = AccessTools.Field(typeof(HoverInfoHold), "onComplete"); private static readonly MethodInfo OnCompleteMethod = AccessTools.Method(typeof(HoverInfoHold), "OnComplete", (Type[])null, (Type[])null); private static readonly List KnownNextPageButtons = new List(4); public static bool IsEnabled { get { if (ConfigManager.EnableFreePages != null) { return ConfigManager.EnableFreePages.Value; } return false; } } public static void ApplyFreeNextPage(DirectiveWindow window) { RegisterWindow(window); } public static void RegisterWindow(DirectiveWindow window) { if ((Object)(object)window == (Object)null || NextPageButtonField == null) { return; } try { object? value = NextPageButtonField.GetValue(window); HoverInfoHold val = (HoverInfoHold)((value is HoverInfoHold) ? value : null); if ((Object)(object)val == (Object)null) { return; } PruneKnown(); for (int i = 0; i < KnownNextPageButtons.Count; i++) { if (KnownNextPageButtons[i] == val) { return; } } KnownNextPageButtons.Add(val); ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)$"[FreePages] Tracked next-page button id={((Object)val).GetInstanceID()} (known={KnownNextPageButtons.Count}). Free={IsEnabled}"); } } catch (Exception ex) { ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogWarning((object)("Free pages register failed: " + ex.Message)); } } } public static void RefreshOpenWindows() { try { DirectiveWindow[] array = Object.FindObjectsOfType(true); if (array != null) { for (int i = 0; i < array.Length; i++) { RegisterWindow(array[i]); } ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)$"[FreePages] SettingChanged → Free={IsEnabled}, trackedButtons={KnownNextPageButtons.Count}"); } } } catch (Exception ex) { ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogWarning((object)("Free pages refresh failed: " + ex.Message)); } } } public static bool IsNextPageButton(HoverInfoHold hold) { if ((Object)(object)hold == (Object)null) { return false; } PruneKnown(); for (int i = 0; i < KnownNextPageButtons.Count; i++) { if (KnownNextPageButtons[i] == hold) { return true; } } try { if (NextPageButtonField == null) { return false; } DirectiveWindow componentInParent = ((Component)hold).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && (object)/*isinst with value type is only supported in some contexts*/ == hold) { KnownNextPageButtons.Add(hold); return true; } } catch { } return false; } private static void PruneKnown() { for (int num = KnownNextPageButtons.Count - 1; num >= 0; num--) { if ((Object)(object)KnownNextPageButtons[num] == (Object)null) { KnownNextPageButtons.RemoveAt(num); } } } internal static float GetHoldDuration(HoverInfoHold hold) { if ((Object)(object)hold == (Object)null || HoldDurationField == null) { return 1f; } try { return (float)HoldDurationField.GetValue(hold); } catch { return 1f; } } internal static Action GetOnCompleteDelegate(HoverInfoHold hold) { if ((Object)(object)hold == (Object)null || OnCompleteMethod == null) { return null; } return (Action)Delegate.CreateDelegate(typeof(Action), hold, OnCompleteMethod); } internal static void InvokeOnCompleteEvent(HoverInfoHold hold) { try { object? obj = OnCompleteEventField?.GetValue(hold); object? obj2 = ((obj is UnityEvent) ? obj : null); if (obj2 != null) { ((UnityEvent)obj2).Invoke(); } } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)("Free pages onComplete invoke: " + ex.Message)); } } } } [HarmonyPatch(typeof(HoverInfoHold), "HasUnlockAction")] internal static class FreePagesHasUnlockActionPatch { private static bool Prefix(HoverInfoHold __instance, out UnlockActionParams data, ref bool __result) { //IL_0001: 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) data = default(UnlockActionParams); try { if (!FreePages.IsEnabled || !FreePages.IsNextPageButton(__instance)) { return true; } data.Duration = FreePages.GetHoldDuration(__instance); data.OnComplete = FreePages.GetOnCompleteDelegate(__instance); data.Cost = null; __result = true; return false; } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)("Free pages HasUnlockAction: " + ex.Message)); } data = default(UnlockActionParams); return true; } } } [HarmonyPatch(typeof(HoverInfoHold), "OnComplete")] internal static class FreePagesOnCompletePatch { private static bool Prefix(HoverInfoHold __instance) { try { if (!FreePages.IsEnabled || !FreePages.IsNextPageButton(__instance)) { return true; } FreePages.InvokeOnCompleteEvent(__instance); return false; } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)("Free pages OnComplete: " + ex.Message)); } return true; } } } [HarmonyPatch(typeof(HoverInfoHold), "GetPrimaryBinding")] internal static class FreePagesGetPrimaryBindingPatch { private static bool Prefix(HoverInfoHold __instance, out InputAction binding, out string label, ref bool __result) { //IL_001f: 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) binding = null; label = null; try { if (!FreePages.IsEnabled || !FreePages.IsNextPageButton(__instance)) { return true; } MenuActions menu = PlayerInput.Controls.Menu; binding = ((MenuActions)(ref menu)).Click; label = TextBlocks.GetString("activate"); __result = true; return false; } catch { binding = null; label = null; return true; } } } [HarmonyPatch(typeof(DirectiveWindow), "OnOpen")] internal static class FreePagesDirectiveWindowOnOpenPatch { private static void Prefix(DirectiveWindow __instance) { FreePages.RegisterWindow(__instance); } private static void Postfix(DirectiveWindow __instance) { FreePages.RegisterWindow(__instance); } } [HarmonyPatch(typeof(DirectiveWindow), "SetupDirectives")] internal static class FreePagesSetupDirectivesPatch { private static void Postfix(DirectiveWindow __instance) { FreePages.RegisterWindow(__instance); } } public static class PathDebug { private const bool SparrohMode = false; private static readonly FieldInfo PropertiesField = AccessTools.Field(typeof(DirectiveInstance), "properties"); private static InputAction _forceCompleteAction; public static void Setup() { try { InputAction forceCompleteAction = _forceCompleteAction; if (forceCompleteAction != null) { forceCompleteAction.Dispose(); } _forceCompleteAction = null; } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)("Could not bind force-complete key: " + ex.Message)); } } } public static void Dispose() { InputAction forceCompleteAction = _forceCompleteAction; if (forceCompleteAction != null) { forceCompleteAction.Disable(); } InputAction forceCompleteAction2 = _forceCompleteAction; if (forceCompleteAction2 != null) { forceCompleteAction2.Dispose(); } _forceCompleteAction = null; } public static bool TryForceCompleteCurrent() { PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { return false; } int num = instance.currentDefaultDirective; if (num < 0 || num >= instance.defaultDirectives.Length) { num = -1; for (int i = 0; i < instance.defaultDirectives.Length; i++) { if (instance.defaultDirectives[i] != null && instance.defaultDirectives[i].IsActive) { num = i; break; } } } if (num < 0) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)"Force-complete: no active directive."); } return false; } DirectiveInstance val = instance.defaultDirectives[num]; if (val == null) { return false; } try { if (val.IsActive) { val.CompleteAllProperties(); } else { ForceCompleteProperties(val); } if (!val.IsComplete) { ForceCompleteProperties(val); } ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogInfo((object)$"Force-completed directive index {num}."); } DirectiveWindow val2 = Object.FindObjectOfType(); if (val2 != null) { val2.SetupDirectives(true); } if (PathLogic.IsOnCurrentPath(num) && val.CanClaimRewards()) { val.ClaimRewards(); if (val2 != null) { val2.SetupDirectives(true); } } return true; } catch (Exception ex) { ManualLogSource log3 = BacklogImprovementsPlugin.Log; if (log3 != null) { log3.LogError((object)("Force-complete failed: " + ex.Message)); } return false; } } private static void ForceCompleteProperties(DirectiveInstance directive) { if (directive == null || PropertiesField == null || !(PropertiesField.GetValue(directive) is IList list)) { return; } for (int i = 0; i < list.Count; i++) { object? obj = list[i]; DirectivePropertyInstance val = (DirectivePropertyInstance)((obj is DirectivePropertyInstance) ? obj : null); if (val != null) { val.Current = val.Target; } } if (directive.IsActive) { directive.Deactivate(); } } } public static class PathLogic { private static bool _autoClaimBusy; private static bool _activateBusy; public static bool IsEditing { get; private set; } public static void SetEditing(bool editing) { IsEditing = editing; } public static int IndexOf(DirectiveInstance directive) { PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null || directive == null) { return -1; } return Array.IndexOf(instance.defaultDirectives, directive); } public static bool IsOnCurrentPath(int index) { return PathStore.Contains(PathStore.CurrentPage, index); } public static bool IsOnCurrentPath(DirectiveInstance directive) { int num = IndexOf(directive); if (num >= 0) { return IsOnCurrentPath(num); } return false; } public static int GetNextPathIndex() { PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { return -1; } List path = PathStore.GetPath(PathStore.CurrentPage); for (int i = 0; i < path.Count; i++) { int num = path[i]; if (num >= 0 && num < instance.defaultDirectives.Length) { DirectiveInstance val = instance.defaultDirectives[num]; if (val != null && (!val.IsComplete || !val.HasClaimedRewards)) { return num; } } } return -1; } public static bool TryActivateNext(DirectiveWindow window = null) { if (_activateBusy || IsEditing) { return false; } ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if (enablePreselect != null && !enablePreselect.Value) { return false; } PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { return false; } if (HasBlockingDirective(instance)) { return false; } int nextPathIndex = GetNextPathIndex(); if (nextPathIndex < 0) { return false; } DirectiveInstance val = instance.defaultDirectives[nextPathIndex]; if (val == null || !val.CanBeActivated()) { return false; } _activateBusy = true; try { val.Activate(); if (val.IsDefault()) { instance.currentDefaultDirective = nextPathIndex; } if (window != null) { window.SetupDirectives(true); } ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)$"Activated path node {nextPathIndex} (tier {val.Tier})."); } return true; } catch (Exception ex) { ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogError((object)$"Failed to activate path node {nextPathIndex}: {ex.Message}"); } return false; } finally { _activateBusy = false; } } public static bool HasBlockingDirective(PlayerData data = null) { data = data ?? PlayerData.Instance; if (data?.defaultDirectives == null) { return false; } for (int i = 0; i < data.defaultDirectives.Length; i++) { DirectiveInstance val = data.defaultDirectives[i]; if (val != null) { if (val.IsActive) { return true; } if (val.IsComplete && !val.HasClaimedRewards) { return true; } } } return false; } public static bool TryAutoClaim(DirectiveWindow window = null) { if (IsEditing || _autoClaimBusy) { return false; } ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if (enablePreselect != null && !enablePreselect.Value) { return false; } PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { return false; } int currentDefaultDirective = instance.currentDefaultDirective; if (currentDefaultDirective < 0 || currentDefaultDirective >= instance.defaultDirectives.Length) { return false; } if (!IsOnCurrentPath(currentDefaultDirective)) { return false; } DirectiveInstance val = instance.defaultDirectives[currentDefaultDirective]; if (val == null || !val.CanClaimRewards()) { return false; } _autoClaimBusy = true; try { val.ClaimRewards(); if (window != null) { window.SetupDirectives(true); } ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)$"Auto-claimed path node {currentDefaultDirective}."); } return true; } catch (Exception ex) { ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogError((object)("Auto-claim failed: " + ex.Message)); } return false; } finally { _autoClaimBusy = false; } } public static bool TryAutoProgress(DirectiveWindow window = null) { if (IsEditing) { return false; } ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if (enablePreselect != null && !enablePreselect.Value) { return false; } bool num = TryAutoClaim(window); bool flag = false; if (!HasBlockingDirective()) { flag = TryActivateNext(window); } return num || flag; } public static void OnDirectiveCompleted(DirectiveInstance completed, DirectiveWindow window = null) { if (IsEditing || completed == null) { return; } ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if ((enablePreselect != null && !enablePreselect.Value) || !IsOnCurrentPath(completed)) { return; } if ((Object)(object)window == (Object)null) { window = Object.FindObjectOfType(); } if (completed.CanClaimRewards()) { if (_autoClaimBusy) { return; } _autoClaimBusy = true; try { int num = IndexOf(completed); completed.ClaimRewards(); if (window != null) { window.SetupDirectives(true); } ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)("Auto-claimed completed path node " + ((num >= 0) ? num.ToString() : "?") + ".")); } return; } catch (Exception ex) { ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogError((object)("Auto-claim on complete failed: " + ex.Message)); } return; } finally { _autoClaimBusy = false; } } if (!HasBlockingDirective()) { TryActivateNext(window); } } public static void OnDirectiveClaimed(DirectiveInstance claimed, DirectiveWindow window = null) { if (!IsEditing && claimed != null) { ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if ((enablePreselect == null || enablePreselect.Value) && IsOnCurrentPath(claimed)) { TryActivateNext(window); } } } public static bool ShouldAllowActivation(DirectiveInstance directive, out bool forceTrue) { forceTrue = false; if (directive == null) { return true; } if (IsEditing) { forceTrue = false; return false; } int num = IndexOf(directive); if (num < 0 || !IsOnCurrentPath(num)) { return true; } int nextPathIndex = GetNextPathIndex(); if (num != nextPathIndex) { return true; } if (directive.IsActive || directive.IsComplete) { return true; } PlayerData instance = PlayerData.Instance; if (instance == null) { return true; } for (int i = 0; i < instance.defaultDirectives.Length; i++) { DirectiveInstance val = instance.defaultDirectives[i]; if (val != null && val != directive && (val.IsActive || (val.IsComplete && !val.HasClaimedRewards))) { return true; } } forceTrue = true; return false; } } [HarmonyPatch(typeof(DirectiveInstance), "Complete")] internal static class PathDirectiveCompletePatch { private static void Postfix(DirectiveInstance __instance) { try { ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if ((enablePreselect == null || enablePreselect.Value) && !PathLogic.IsEditing) { PathLogic.OnDirectiveCompleted(__instance); } } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogError((object)("DirectiveInstance.Complete postfix: " + ex.Message)); } } } } [HarmonyPatch(typeof(DirectiveInstance), "ClaimRewards")] internal static class PathDirectiveClaimPatch { private static void Postfix(DirectiveInstance __instance) { try { ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if ((enablePreselect == null || enablePreselect.Value) && !PathLogic.IsEditing) { DirectiveWindow window = Object.FindObjectOfType(); PathLogic.OnDirectiveClaimed(__instance, window); } } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogError((object)("DirectiveInstance.ClaimRewards postfix: " + ex.Message)); } } } } [HarmonyPatch(typeof(DirectiveInstance), "CanBeActivated")] internal static class PathCanBeActivatedPatch { private static bool Prefix(DirectiveInstance __instance, ref bool __result) { try { ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if (enablePreselect != null && !enablePreselect.Value) { return true; } if (PathLogic.IsEditing) { __result = false; return false; } if (!PathLogic.ShouldAllowActivation(__instance, out var forceTrue) && forceTrue) { __result = true; return false; } return true; } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogError((object)("CanBeActivated patch: " + ex.Message)); } return true; } } } [HarmonyPatch(typeof(PlayerData), "GetCurrrentDirectiveTier")] internal static class PathDirectiveTierPatch { private static void Postfix(ref int __result) { if (PathLogic.IsEditing) { __result = 99; } } } [HarmonyPatch(typeof(DirectiveButton), "Setup")] internal static class PathDirectiveButtonSetupPatch { private static readonly MethodInfo SetupAvailableMethod = AccessTools.Method(typeof(DirectiveButton), "SetupAvailable", (Type[])null, (Type[])null); private static void Postfix(DirectiveButton __instance, DirectiveInstance directive) { if (!PathLogic.IsEditing || directive == null) { return; } try { if (!directive.IsComplete || !directive.HasClaimedRewards) { SetupAvailableMethod?.Invoke(__instance, null); } } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)("SetupAvailable force failed: " + ex.Message)); } } } } public static class PathStore { private static readonly Dictionary> PagePaths = new Dictionary>(); private static string FilePath => Path.Combine(Paths.ConfigPath, "sparroh.backlogimprovements.txt"); private static string LegacyFilePath => Path.Combine(Paths.ConfigPath, "sparroh.preselectbacklog.txt"); public static IReadOnlyDictionary> All => PagePaths; public static int CurrentPage { get { try { return PlayerData.Instance?.currentDirectivePage ?? 0; } catch { return 0; } } } public static void Load() { PagePaths.Clear(); try { string text = FilePath; if (!File.Exists(text) && File.Exists(LegacyFilePath)) { text = LegacyFilePath; ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)"Migrating preselected paths from legacy PreselectBacklog config."); } } if (!File.Exists(text)) { return; } string[] array = File.ReadAllLines(text); foreach (string text2 in array) { if (string.IsNullOrWhiteSpace(text2)) { continue; } string[] array2 = text2.Split(','); if (array2.Length == 2 && int.TryParse(array2[0].Trim(), out var result) && int.TryParse(array2[1].Trim(), out var result2)) { if (!PagePaths.TryGetValue(result, out var value)) { value = new List(); PagePaths[result] = value; } if (!value.Contains(result2)) { value.Add(result2); } } } foreach (KeyValuePair> item in PagePaths.ToList()) { PagePaths[item.Key] = SortByTier(item.Value); } if (text == LegacyFilePath && PagePaths.Count > 0) { Save(); } } catch (Exception ex) { ManualLogSource log2 = BacklogImprovementsPlugin.Log; if (log2 != null) { log2.LogError((object)("Failed to load preselected paths: " + ex.Message)); } } } public static void Save() { try { Directory.CreateDirectory(Paths.ConfigPath); List list = new List(); foreach (KeyValuePair> item in PagePaths.OrderBy((KeyValuePair> k) => k.Key)) { foreach (int item2 in item.Value) { list.Add($"{item.Key},{item2}"); } } File.WriteAllLines(FilePath, list); } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogError((object)("Failed to save preselected paths: " + ex.Message)); } } } public static List GetPath(int page) { if (!PagePaths.TryGetValue(page, out var value)) { value = new List(); PagePaths[page] = value; } return value; } public static List GetPathCopy(int page) { return new List(GetPath(page)); } public static bool HasPath(int page) { if (PagePaths.TryGetValue(page, out var value)) { return value.Count > 0; } return false; } public static bool Contains(int page, int index) { if (PagePaths.TryGetValue(page, out var value)) { return value.Contains(index); } return false; } public static void SetPath(int page, List indices) { PagePaths[page] = SortByTier(indices ?? new List()); Save(); } public static void ClearPage(int page) { if (PagePaths.ContainsKey(page)) { PagePaths[page] = new List(); Save(); } } public static void ToggleSelection(int page, int index) { List path = GetPath(page); if (path.Contains(index)) { path.Remove(index); Save(); return; } PlayerData data = PlayerData.Instance; if (data?.defaultDirectives == null || index < 0 || index >= data.defaultDirectives.Length) { return; } DirectiveInstance val = data.defaultDirectives[index]; if (val == null) { return; } int selectedTier = val.Tier; path.RemoveAll(delegate(int i) { if (i < 0 || i >= data.defaultDirectives.Length) { return true; } DirectiveInstance val2 = data.defaultDirectives[i]; return val2 == null || val2.Tier == selectedTier; }); path.Add(index); PagePaths[page] = SortByTier(path); Save(); } public static List SortByTier(List indices) { PlayerData data = PlayerData.Instance; if (data?.defaultDirectives == null || indices == null) { return indices ?? new List(); } return (from i in indices.Where((int i) => i >= 0 && i < data.defaultDirectives.Length && data.defaultDirectives[i] != null).Distinct() orderby data.defaultDirectives[i].Tier, i select i).ToList(); } } public static class PathToolbar { public static void Build(BacklogUI.WindowState state, RectTransform root) { state.EditButton = UIButton.Create((Transform)(object)root, "Edit Path", (Action)delegate { OnEditClicked(state); }, (UIButtonStyle)1, (string)null, (float?)32f); BacklogUI.ApplyCompactLabel(state.EditButton); BacklogUI.FitButtonWidth(state.EditButton, 120f); state.ClearButton = UIButton.Create((Transform)(object)root, "Clear", (Action)delegate { OnClearClicked(state); }, (UIButtonStyle)2, (string)null, (float?)32f); BacklogUI.ApplyCompactLabel(state.ClearButton); BacklogUI.FitButtonWidth(state.ClearButton, 80f); } public static bool TryHandleClick(DirectiveButton button, BacklogUI.WindowState state) { if (state == null) { return false; } if (BacklogUI.IsAnimating(state.Window)) { return true; } DirectiveInstance directive = BacklogUI.GetDirective(button); int num = PathLogic.IndexOf(directive); if (num < 0) { return true; } if (directive != null && directive.IsComplete && directive.HasClaimedRewards) { UIDialog.Alert("Cannot Select", "That directive is already finished.", (Action)null, "OK"); return true; } PathStore.ToggleSelection(PathStore.CurrentPage, num); BacklogUI.RefreshAll(state); PathVisualizer.Refresh(state.Window); DirectiveWindow window = state.Window; if (window != null) { window.SetupDirectives(false); } return true; } public static void EnterEdit(BacklogUI.WindowState state) { PathLogic.SetEditing(editing: true); BacklogUI.RefreshAll(state); DirectiveWindow window = state.Window; if (window != null) { window.SetupDirectives(false); } PathVisualizer.Refresh(state.Window); } public static void ExitEdit(BacklogUI.WindowState state, bool activateNext, bool refreshWindow) { if (!PathLogic.IsEditing) { return; } PathLogic.SetEditing(editing: false); PathStore.Save(); BacklogUI.RefreshAll(state); if (refreshWindow) { DirectiveWindow window = state.Window; if (window != null) { window.SetupDirectives(false); } } PathVisualizer.Refresh(state.Window); if (activateNext && !PathLogic.HasBlockingDirective()) { PathLogic.TryActivateNext(state.Window); } } public static void RefreshLabels(BacklogUI.WindowState state) { if (state.EditButton != null || state.ClearButton != null) { int count = PathStore.GetPath(PathStore.CurrentPage).Count; bool isEditing = PathLogic.IsEditing; if (state.EditButton != null) { state.EditButton.SetText(isEditing ? "Done" : "Edit Path"); state.EditButton.SetStyle((UIButtonStyle)((!isEditing) ? 1 : 3)); BacklogUI.FitButtonWidth(state.EditButton, 120f); state.EditButton.SetInteractable(!state.RerollSelecting); } if (state.ClearButton != null) { state.ClearButton.SetInteractable((count > 0 || isEditing) && !state.RerollSelecting); } } } private static void OnEditClicked(BacklogUI.WindowState state) { if (!((Object)(object)state?.Window == (Object)null) && !BacklogUI.IsAnimating(state.Window)) { if (PathLogic.IsEditing) { ExitEdit(state, activateNext: true, refreshWindow: true); return; } RerollToolbar.ExitSelection(state); EnterEdit(state); } } private static void OnClearClicked(BacklogUI.WindowState state) { if ((Object)(object)state?.Window == (Object)null || BacklogUI.IsAnimating(state.Window)) { return; } int page = PathStore.CurrentPage; if (!PathStore.HasPath(page)) { UIDialog.Alert("Nothing to Clear", "No preselected path on this page.", (Action)null, "OK"); return; } UIDialog.Confirm("Clear Path", "Remove the preselected path for this backlog page?", (Action)delegate { PathStore.ClearPage(page); BacklogUI.RefreshAll(state); PathVisualizer.Refresh(state.Window); DirectiveWindow window = state.Window; if (window != null) { window.SetupDirectives(false); } }, (Action)null, "Clear", "Cancel"); } } public static class PathVisualizer { private sealed class VisualState { public readonly List Badges = new List(); public readonly List Lines = new List(); public readonly List Overlays = new List(); public Transform LineParent; public DirectiveWindow Window; } private const float LineWidth = 4f; private const float BadgeSize = 22f; private static readonly FieldInfo DirectivesField = AccessTools.Field(typeof(DirectiveWindow), "directives"); private static readonly FieldInfo LinesField = AccessTools.Field(typeof(DirectiveWindow), "lines"); private static readonly FieldInfo OutlineField = AccessTools.Field(typeof(DirectiveButton), "outline"); private static readonly Dictionary States = new Dictionary(); public static void Refresh(DirectiveWindow window) { if ((Object)(object)window == (Object)null) { return; } ConfigEntry enablePreselect = ConfigManager.EnablePreselect; if (enablePreselect != null && !enablePreselect.Value) { Clear(window); return; } int instanceID = ((Object)window).GetInstanceID(); if (!States.TryGetValue(instanceID, out var value)) { value = new VisualState { Window = window }; States[instanceID] = value; } else { value.Window = window; } EnsureLineParent(value); ClearVisuals(value); if (!(DirectivesField?.GetValue(window) is DirectiveButton[] array)) { return; } List path = PathStore.GetPath(PathStore.CurrentPage); if (path == null || path.Count == 0) { return; } List list = PathStore.SortByTier(new List(path)); for (int i = 0; i < list.Count; i++) { int num = list[i]; if (num >= 0 && num < array.Length && !((Object)(object)array[num] == (Object)null)) { CreateBadge(value, array[num], i + 1); CreateOverlay(value, array[num]); ApplyOutline(array[num], selected: true); } } for (int j = 0; j < list.Count - 1; j++) { int num2 = list[j]; int num3 = list[j + 1]; if (num2 >= 0 && num3 >= 0 && num2 < array.Length && num3 < array.Length && !((Object)(object)array[num2] == (Object)null) && !((Object)(object)array[num3] == (Object)null)) { CreateLine(value, array[num2], array[num3]); } } } public static void Clear(DirectiveWindow window) { if (!((Object)(object)window == (Object)null)) { int instanceID = ((Object)window).GetInstanceID(); if (States.TryGetValue(instanceID, out var value)) { ClearVisuals(value); UIHelpers.DestroySafe((Object)(object)(((Object)(object)value.LineParent != (Object)null && ((Object)value.LineParent).name == "PreselectPathLines") ? ((Component)value.LineParent).gameObject : null)); States.Remove(instanceID); } } } public static void CleanupAll() { foreach (KeyValuePair state in States) { ClearVisuals(state.Value); } States.Clear(); } private static void EnsureLineParent(VisualState state) { if ((Object)(object)state.LineParent != (Object)null) { return; } try { if (LinesField?.GetValue(state.Window) is IList { Count: >0 } list) { object? obj = list[0]; Component val = (Component)((obj is Component) ? obj : null); if ((Object)(object)val != (Object)null) { state.LineParent = val.transform.parent; return; } } } catch { } Transform transform = ((Component)state.Window).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); RectTransform val3 = UIFactory.CreateRect("PreselectPathLines", (Transform)(object)val2); UIHelpers.SetFillParent(val3, 0f); ((Transform)val3).SetAsFirstSibling(); Image component = ((Component)val3).gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } Graphic component2 = ((Component)val3).gameObject.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.raycastTarget = false; } state.LineParent = (Transform)(object)val3; } private static void CreateLine(VisualState state, DirectiveButton from, DirectiveButton to) { //IL_0051: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: 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_0169: 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) if ((Object)(object)state.LineParent == (Object)null) { return; } Transform transform = ((Component)from).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); Transform transform2 = ((Component)to).transform; RectTransform val2 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); Transform lineParent = state.LineParent; RectTransform val3 = (RectTransform)(object)((lineParent is RectTransform) ? lineParent : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null) && !((Object)(object)val3 == (Object)null)) { Vector2 val4 = Vector2.op_Implicit(((Transform)val3).InverseTransformPoint(((Transform)val).position)); Vector2 val5 = Vector2.op_Implicit(((Transform)val3).InverseTransformPoint(((Transform)val2).position)) - val4; float magnitude = ((Vector2)(ref val5)).magnitude; if (!(magnitude < 1f)) { Vector2 val6 = val5 / magnitude; Rect rect = val.rect; float num = ((Rect)(ref rect)).height * 0.5f; Vector2 anchoredPosition = val4 + val6 * num; float num2 = Mathf.Max(0f, magnitude - num * 2f); Image val7 = UIFactory.CreateImage("PreselectPathLine", state.LineParent, UIColors.WithAlpha(UIColors.Macaroon, 0.95f), false); UIFactory.ApplyWhiteSprite(val7); RectTransform rectTransform = ((Graphic)val7).rectTransform; Vector2 val8 = default(Vector2); ((Vector2)(ref val8))..ctor(0.5f, 0.5f); rectTransform.anchorMax = val8; rectTransform.anchorMin = val8; rectTransform.pivot = new Vector2(0.5f, 0f); rectTransform.sizeDelta = new Vector2(4f, num2); rectTransform.anchoredPosition = anchoredPosition; ((Transform)rectTransform).localRotation = Quaternion.FromToRotation(Vector2.op_Implicit(Vector2.up), Vector2.op_Implicit(val6)); ((Transform)rectTransform).localScale = Vector3.one; ((Transform)rectTransform).SetAsFirstSibling(); state.Lines.Add(((Component)val7).gameObject); } } } private static void CreateBadge(VisualState state, DirectiveButton button, int order) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_00ab: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)button).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (!((Object)(object)val == (Object)null)) { Image val2 = UIFactory.CreateImage("PreselectBadge", (Transform)(object)val, UIColors.Macaroon, false); UIFactory.ApplyWhiteSprite(val2); RectTransform rectTransform = ((Graphic)val2).rectTransform; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(1f, 1f); rectTransform.anchorMax = val3; rectTransform.anchorMin = val3; rectTransform.pivot = new Vector2(1f, 1f); rectTransform.sizeDelta = new Vector2(22f, 22f); rectTransform.anchoredPosition = new Vector2(-2f, -2f); ((Transform)rectTransform).SetAsLastSibling(); TextMeshProUGUI obj = UIFactory.CreateTmp("Num", (Transform)(object)rectTransform, order.ToString(), 14f, (Color?)UIColors.PanelBg, (TextAlignmentOptions)514, false); UIHelpers.SetFillParent(((TMP_Text)obj).rectTransform, 1f); ((Graphic)obj).raycastTarget = false; ((TMP_Text)obj).fontStyle = (FontStyles)1; state.Badges.Add(((Component)val2).gameObject); } } private static void CreateOverlay(VisualState state, DirectiveButton button) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)button).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (!((Object)(object)val == (Object)null)) { Image val2 = UIFactory.CreateImage("PreselectOverlay", (Transform)(object)val, UIColors.WithAlpha(UIColors.Macaroon, PathLogic.IsEditing ? 0.3f : 0.18f), false); UIFactory.ApplyWhiteSprite(val2); UIHelpers.SetFillParent(((Graphic)val2).rectTransform, 2f); ((Transform)((Graphic)val2).rectTransform).SetAsLastSibling(); state.Overlays.Add(((Component)val2).gameObject); } } private static void ApplyOutline(DirectiveButton button, bool selected) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) try { object? obj = OutlineField?.GetValue(button); PolygonOutline val = (PolygonOutline)((obj is PolygonOutline) ? obj : null); if (!((Object)(object)val == (Object)null) && selected) { ((Component)val).gameObject.SetActive(true); ((Graphic)val).color = UIColors.Macaroon; ((OutlineGraphic)val).SetValue(PathLogic.IsEditing ? 10f : 7f); } } catch { } } private static void ClearVisuals(VisualState state) { if (state != null) { for (int i = 0; i < state.Lines.Count; i++) { UIHelpers.DestroySafe((Object)(object)state.Lines[i]); } state.Lines.Clear(); for (int j = 0; j < state.Badges.Count; j++) { UIHelpers.DestroySafe((Object)(object)state.Badges[j]); } state.Badges.Clear(); for (int k = 0; k < state.Overlays.Count; k++) { UIHelpers.DestroySafe((Object)(object)state.Overlays[k]); } state.Overlays.Clear(); } } } [BepInPlugin("sparroh.backlogimprovements", "BacklogImprovements", "2.1.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] [MycoMod(/*Could not decode attribute arguments.*/)] public class BacklogImprovementsPlugin : BaseUnityPlugin { public const string PluginGUID = "sparroh.backlogimprovements"; public const string PluginName = "BacklogImprovements"; public const string PluginVersion = "2.1.5"; public const string LegacyPreselectGuid = "sparroh.preselectbacklog"; internal static ManualLogSource Log; public static BacklogImprovementsPlugin Instance; private Harmony _harmony; private void Awake() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Instance = this; ConfigManager.Initialize(((BaseUnityPlugin)this).Config, Log); PathStore.Load(); try { _harmony = new Harmony("sparroh.backlogimprovements"); _harmony.PatchAll(); Log.LogInfo((object)"BacklogImprovements v2.1.5 loaded."); } catch (Exception arg) { Log.LogError((object)string.Format("Failed to initialize {0}: {1}", "BacklogImprovements", arg)); } PathDebug.Setup(); } private void Update() { ConfigManager.Tick(); } private void OnDestroy() { try { ConfigManager.Dispose(); PathDebug.Dispose(); BacklogUI.CleanupAll(); PathStore.Save(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Cleanup failed: " + ex.Message)); } } } } public static class RerollLogic { public static int CostPerDirective => Mathf.Max(0, ConfigManager.CostPerDirective?.Value ?? 50); public static bool IsEligible(DirectiveInstance directive) { if (directive == null) { return false; } if (!directive.IsActive) { return !directive.IsComplete; } return false; } public static int CountEligible() { PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { return 0; } int num = 0; for (int i = 0; i < instance.defaultDirectives.Length; i++) { if (IsEligible(instance.defaultDirectives[i])) { num++; } } return num; } public static int GetPageRerollCost() { return CountEligible() * CostPerDirective; } public static int GetSingleRerollCost() { return CostPerDirective; } public static bool CanAfford(int cost) { if (cost <= 0) { return true; } PlayerData instance = PlayerData.Instance; Global instance2 = Global.Instance; PlayerResource val = ((instance2 != null) ? instance2.ScripResource : null); if (instance == null || (Object)(object)val == (Object)null) { return false; } return instance.CanAffordCost(val, cost); } public static bool TryCharge(int cost) { if (cost <= 0) { return true; } PlayerData instance = PlayerData.Instance; Global instance2 = Global.Instance; PlayerResource val = ((instance2 != null) ? instance2.ScripResource : null); if (instance == null || (Object)(object)val == (Object)null) { return false; } return instance.TryRemoveResource(val, cost); } public static bool TryRerollSingle(int index, out string error) { error = null; PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { error = "Player data not ready."; return false; } if (index < 0 || index >= instance.defaultDirectives.Length) { error = "Invalid directive."; return false; } if (!IsEligible(instance.defaultDirectives[index])) { error = "That directive cannot be rerolled (active or already complete)."; return false; } int singleRerollCost = GetSingleRerollCost(); if (!CanAfford(singleRerollCost)) { error = $"Not enough gats (need {singleRerollCost})."; return false; } if (!TryCharge(singleRerollCost)) { error = $"Not enough gats (need {singleRerollCost})."; return false; } ReplaceAt(instance, index); return true; } public static bool TryRerollPage(out int rerolledCount, out string error) { rerolledCount = 0; error = null; PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null) { error = "Player data not ready."; return false; } List list = new List(); for (int i = 0; i < instance.defaultDirectives.Length; i++) { if (IsEligible(instance.defaultDirectives[i])) { list.Add(i); } } if (list.Count == 0) { error = "No eligible directives to reroll."; return false; } int num = list.Count * CostPerDirective; if (!CanAfford(num)) { error = $"Not enough gats (need {num})."; return false; } if (!TryCharge(num)) { error = $"Not enough gats (need {num})."; return false; } for (int j = 0; j < list.Count; j++) { ReplaceAt(instance, list[j]); } rerolledCount = list.Count; return true; } private static void ReplaceAt(PlayerData data, int index) { int tierFromDirectiveIndex = data.GetTierFromDirectiveIndex(index); try { DirectiveInstance obj = data.defaultDirectives[index]; if (obj != null) { obj.Destroy(); } } catch (Exception ex) { ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogWarning((object)$"Destroy directive {index}: {ex.Message}"); } } data.defaultDirectives[index] = data.CreateDirective(tierFromDirectiveIndex); } public static int IndexOf(DirectiveInstance directive) { PlayerData instance = PlayerData.Instance; if (instance?.defaultDirectives == null || directive == null) { return -1; } return Array.IndexOf(instance.defaultDirectives, directive); } } public static class RerollToolbar { public static void Build(BacklogUI.WindowState state, RectTransform root) { state.PageButton = UIButton.Create((Transform)(object)root, "Reroll Page", (Action)delegate { OnRerollPageClicked(state); }, (UIButtonStyle)1, (string)null, (float?)32f); BacklogUI.ApplyCompactLabel(state.PageButton); BacklogUI.FitButtonWidth(state.PageButton, 120f); state.SingleButton = UIButton.Create((Transform)(object)root, "Reroll One", (Action)delegate { OnRerollOneClicked(state); }, (UIButtonStyle)0, (string)null, (float?)32f); BacklogUI.ApplyCompactLabel(state.SingleButton); BacklogUI.FitButtonWidth(state.SingleButton, 120f); state.CancelButton = UIButton.Create((Transform)(object)root, "Cancel", (Action)delegate { ExitSelection(state); }, (UIButtonStyle)2, (string)null, (float?)32f); BacklogUI.ApplyCompactLabel(state.CancelButton); state.CancelButton.SetWidth(90f); state.CancelButton.SetActive(false); } public static bool TryHandleClick(DirectiveButton button, BacklogUI.WindowState state) { if (state == null || !state.RerollSelecting) { return false; } if (BacklogUI.IsAnimating(state.Window)) { return true; } DirectiveInstance directive = BacklogUI.GetDirective(button); int index = RerollLogic.IndexOf(directive); if (index < 0 || !RerollLogic.IsEligible(directive)) { UIDialog.Alert("Cannot Reroll", "Only not-started directives can be rerolled.", (Action)null, "OK"); return true; } int singleRerollCost = RerollLogic.GetSingleRerollCost(); string text = directive.GetTitle(); if (string.IsNullOrEmpty(text)) { text = "this directive"; } UIDialog.Confirm("Reroll Directive", $"Reroll {text} for {singleRerollCost} gats?", (Action)delegate { if (RerollLogic.TryRerollSingle(index, out var error)) { ExitSelection(state); DirectiveWindow window = state.Window; if (window != null) { window.SetupDirectives(true); } BacklogUI.RefreshAll(state); BacklogUI.PlayRerollSound(); } else { UIDialog.Alert("Reroll Failed", error ?? "Unknown error.", (Action)null, "OK"); } }, (Action)null, $"Pay {singleRerollCost}", "Cancel"); return true; } public static void EnterSelection(BacklogUI.WindowState state) { state.RerollSelecting = true; if (state.CancelButton != null) { state.CancelButton.SetActive(true); } if (state.SingleButton != null) { state.SingleButton.SetText("Click a directive…"); BacklogUI.FitButtonWidth(state.SingleButton, 120f); } if (state.PageButton != null) { state.PageButton.SetInteractable(false); } if (state.EditButton != null) { state.EditButton.SetInteractable(false); } if (state.ClearButton != null) { state.ClearButton.SetInteractable(false); } RebuildOverlays(state); } public static void ExitSelection(BacklogUI.WindowState state) { if (state != null && state.RerollSelecting) { state.RerollSelecting = false; ClearOverlays(state); if (state.CancelButton != null) { state.CancelButton.SetActive(false); } if (state.PageButton != null) { state.PageButton.SetInteractable(true); } if (state.EditButton != null) { state.EditButton.SetInteractable(true); } BacklogUI.RefreshAll(state); } } public static void RebuildOverlays(BacklogUI.WindowState state) { ClearOverlays(state); if ((Object)(object)state?.Window == (Object)null || !state.RerollSelecting) { return; } DirectiveButton[] directiveButtons = BacklogUI.GetDirectiveButtons(state.Window); if (directiveButtons == null) { return; } foreach (DirectiveButton val in directiveButtons) { if (!((Object)(object)val == (Object)null) && RerollLogic.IsEligible(BacklogUI.GetDirective(val))) { Transform transform = ((Component)val).transform; GameObject val2 = CreateEligibleOverlay((RectTransform)(object)((transform is RectTransform) ? transform : null)); if ((Object)(object)val2 != (Object)null) { state.RerollOverlays.Add(val2); } } } } public static void ClearOverlays(BacklogUI.WindowState state) { if (state != null) { for (int i = 0; i < state.RerollOverlays.Count; i++) { UIHelpers.DestroySafe((Object)(object)state.RerollOverlays[i]); } state.RerollOverlays.Clear(); } } public static void RefreshLabels(BacklogUI.WindowState state) { if ((state.PageButton != null || state.SingleButton != null) && !state.RerollSelecting) { int num = RerollLogic.CountEligible(); int num2 = num * RerollLogic.CostPerDirective; int costPerDirective = RerollLogic.CostPerDirective; bool isEditing = PathLogic.IsEditing; if (state.PageButton != null) { state.PageButton.SetText((num > 0) ? $"Reroll Page ({num2})" : "Reroll Page"); BacklogUI.FitButtonWidth(state.PageButton, 120f); state.PageButton.SetInteractable(!isEditing && num > 0 && RerollLogic.CanAfford(num2)); } if (state.SingleButton != null) { state.SingleButton.SetText((num > 0) ? $"Reroll One ({costPerDirective})" : "Reroll One"); BacklogUI.FitButtonWidth(state.SingleButton, 120f); state.SingleButton.SetInteractable(!isEditing && num > 0 && RerollLogic.CanAfford(costPerDirective)); } } } private static void OnRerollPageClicked(BacklogUI.WindowState state) { if ((Object)(object)state?.Window == (Object)null || BacklogUI.IsAnimating(state.Window)) { return; } ExitSelection(state); PathToolbar.ExitEdit(state, activateNext: false, refreshWindow: true); int num = RerollLogic.CountEligible(); if (num <= 0) { UIDialog.Alert("Nothing to Reroll", "There are no not-started directives on this page.", (Action)null, "OK"); return; } int cost = RerollLogic.GetPageRerollCost(); if (!RerollLogic.CanAfford(cost)) { UIDialog.Alert("Not Enough Gats", $"Need {cost} gats to reroll {num} directive(s).", (Action)null, "OK"); return; } UIDialog.Confirm("Reroll Page", $"Reroll {num} not-started directive(s) for {cost} gats?\nActive and completed directives are kept.", (Action)delegate { if (RerollLogic.TryRerollPage(out var rerolledCount, out var error)) { DirectiveWindow window = state.Window; if (window != null) { window.SetupDirectives(true); } BacklogUI.RefreshAll(state); BacklogUI.PlayRerollSound(); ManualLogSource log = BacklogImprovementsPlugin.Log; if (log != null) { log.LogInfo((object)$"Rerolled {rerolledCount} backlog directive(s) for {cost} gats."); } } else { UIDialog.Alert("Reroll Failed", error ?? "Unknown error.", (Action)null, "OK"); } }, (Action)null, $"Pay {cost}", "Cancel"); } private static void OnRerollOneClicked(BacklogUI.WindowState state) { if ((Object)(object)state?.Window == (Object)null || BacklogUI.IsAnimating(state.Window)) { return; } if (state.RerollSelecting) { ExitSelection(state); return; } if (RerollLogic.CountEligible() <= 0) { UIDialog.Alert("Nothing to Reroll", "There are no not-started directives on this page.", (Action)null, "OK"); return; } int singleRerollCost = RerollLogic.GetSingleRerollCost(); if (!RerollLogic.CanAfford(singleRerollCost)) { UIDialog.Alert("Not Enough Gats", $"Need {singleRerollCost} gats to reroll one directive.", (Action)null, "OK"); return; } PathToolbar.ExitEdit(state, activateNext: false, refreshWindow: true); EnterSelection(state); } private static GameObject CreateEligibleOverlay(RectTransform target) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { return null; } Image obj = UIFactory.CreateImage("RerollEligibleOverlay", (Transform)(object)target, UIColors.WithAlpha(UIColors.ButtonPrimary, 0.35f), false); UIFactory.ApplyWhiteSprite(obj); UIHelpers.SetFillParent(((Graphic)obj).rectTransform, UITheme.S(2f)); ((Transform)((Graphic)obj).rectTransform).SetAsLastSibling(); return ((Component)obj).gameObject; } } namespace BacklogImprovements { public static class MyPluginInfo { public const string PLUGIN_GUID = "BacklogImprovements"; public const string PLUGIN_NAME = "BacklogImprovements"; public const string PLUGIN_VERSION = "2.1.5"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }