using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using FishNet.Object; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("KeyErrorFinn.QuickSell")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("How to QuickSell")] [assembly: AssemblyTitle("KeyErrorFinn.QuickSell")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace KeyErrorFinn.QuickSell { [BepInPlugin("com.keyerrorfinn.quicksell", "How to QuickSell", "1.0.0")] public sealed class QuickSellPlugin : BaseUnityPlugin { public const string PluginGuid = "com.keyerrorfinn.quicksell"; public const string PluginName = "How to QuickSell"; public const string PluginVersion = "1.0.0"; private ConfigEntry _sellAllHotkey; private ConfigEntry _sellFloorItems; private ConfigEntry _sellInventoryItems; private ConfigEntry _onlySellFish; private ConfigEntry _floorSaleWaterBuffer; private QuickSellService _quickSell; private QuickSellSettingsUI _settingsUi; private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) _sellAllHotkey = ((BaseUnityPlugin)this).Config.Bind("General", "Sell all hotkey", new KeyboardShortcut((KeyCode)289, Array.Empty()), "Sell all enabled item groups. You must be the host/server."); _sellFloorItems = ((BaseUnityPlugin)this).Config.Bind("Sale options", "Sell all floor items", false, "Sell dead fish on the loaded island and in the nearby water. Shellfish and other non-fish creatures are excluded."); _sellInventoryItems = ((BaseUnityPlugin)this).Config.Bind("Sale options", "Sell all inventory items", true, "Sell eligible items in the local inventory, including a freshly picked-up item held outside the inventory bar."); _onlySellFish = ((BaseUnityPlugin)this).Config.Bind("Safety", "Only sell fish", true, "When enabled, only fish/creature items are sold. Turn this off to sell all non-quest items with value."); _floorSaleWaterBuffer = ((BaseUnityPlugin)this).Config.Bind("Sale options", "Floor sale water buffer", 30f, "Additional distance beyond the island edge for dead fish floating in nearby water."); _quickSell = new QuickSellService(((BaseUnityPlugin)this).Logger, _sellFloorItems, _sellInventoryItems, _onlySellFish, _floorSaleWaterBuffer); _settingsUi = new QuickSellSettingsUI(((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Press {2} to sell enabled items.", "How to QuickSell", "1.0.0", _sellAllHotkey.Value)); _settingsUi.Install(_sellFloorItems, _sellInventoryItems); } private void Update() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = _sellAllHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { _quickSell.TrySell(); } } private void OnDestroy() { _settingsUi?.Dispose(); } } internal sealed class QuickSellService { private sealed class FieldInfoCache { private readonly FieldInfo _itemSlots = AccessTools.Field(typeof(PlayerInventory), "_itemSlots"); public List GetSlots(PlayerInventory inventory) { return _itemSlots?.GetValue(inventory) as List; } } private readonly ManualLogSource _log; private readonly ConfigEntry _sellFloorItems; private readonly ConfigEntry _sellInventoryItems; private readonly ConfigEntry _onlySellFish; private readonly ConfigEntry _floorSaleWaterBuffer; private readonly FieldInfoCache _fields = new FieldInfoCache(); private readonly HashSet _itemsBeingSold = new HashSet(); public QuickSellService(ManualLogSource log, ConfigEntry sellFloorItems, ConfigEntry sellInventoryItems, ConfigEntry onlySellFish, ConfigEntry floorSaleWaterBuffer) { _log = log; _sellFloorItems = sellFloorItems; _sellInventoryItems = sellInventoryItems; _onlySellFish = onlySellFish; _floorSaleWaterBuffer = floorSaleWaterBuffer; } public void TrySell() { _itemsBeingSold.RemoveWhere((Item item) => (Object)(object)item == (Object)null); if ((Object)(object)Player.LocalPlayer == (Object)null || (Object)(object)Player.LocalPlayer.Inventory == (Object)null) { _log.LogWarning((object)"QuickSell is only available after the local player has spawned."); return; } if ((Object)(object)Server.Instance == (Object)null || !((NetworkBehaviour)Server.Instance).IsServerInitialized) { _log.LogWarning((object)"QuickSell must be used by the lobby host. It cannot change a remote server's inventory or money."); return; } PlayerInventory inventory = Player.LocalPlayer.Inventory; NPC val = SaleRules.FindMoneyNpc(); if ((Object)(object)val == (Object)null) { _log.LogWarning((object)"No money NPC is loaded. Move to the trader island before using QuickSell."); return; } List slots = _fields.GetSlots(inventory); if (slots == null) { _log.LogError((object)"Unable to read the game's inventory slots; the game may have changed."); return; } Item heldItem; bool heldItemIsOutsideInventory; List list = CollectItems(inventory, slots, out heldItem, out heldItemIsOutsideInventory); if (list.Count == 0) { _log.LogInfo((object)"No eligible inventory items to sell."); return; } foreach (Item item in list) { _itemsBeingSold.Add(item); } try { if (_sellInventoryItems.Value) { inventory.ApplySlot(-1); } if (heldItemIsOutsideInventory) { Player.LocalPlayer.Holding.DropItem(false, heldItem); } foreach (Item item2 in list) { if (inventory.HasItemInInventory(item2)) { inventory.RemoveItem(item2); } MoneyManager.SellItem(item2); item2.DestroyItem((byte)2, val.ID); } _log.LogInfo((object)$"Sold {list.Count} item(s) through money NPC {val.ID}."); } catch (Exception arg) { _log.LogError((object)$"QuickSell stopped: {arg}"); } } private List CollectItems(PlayerInventory inventory, List slots, out Item heldItem, out bool heldItemIsOutsideInventory) { List list = new List(); if (_sellInventoryItems.Value) { list.AddRange(from slot in slots select (!((Object)(object)slot == (Object)null)) ? slot.Item : null into item where SaleRules.IsEligibleForSale(item, _onlySellFish.Value) && !_itemsBeingSold.Contains(item) select item); } heldItem = Player.LocalPlayer.Holding.HeldItem; heldItemIsOutsideInventory = _sellInventoryItems.Value && SaleRules.IsEligibleForSale(heldItem, _onlySellFish.Value) && !_itemsBeingSold.Contains(heldItem) && !inventory.HasItemInInventory(heldItem); if (heldItemIsOutsideInventory) { list.Add(heldItem); } if (_sellFloorItems.Value) { list.AddRange(from item in SaleRules.GetFloorFishNearIsland(_floorSaleWaterBuffer.Value, _log) where !_itemsBeingSold.Contains(item) select item); } return list.Distinct().ToList(); } } internal sealed class QuickSellSettingsUI : IDisposable { private readonly ManualLogSource _log; private readonly List _nativeSettingsRows = new List(); public QuickSellSettingsUI(ManualLogSource log) { _log = log; } public void Install(ConfigEntry sellFloorItems, ConfigEntry sellInventoryItems) { TextMeshProUGUI val = ((IEnumerable)Resources.FindObjectsOfTypeAll()).FirstOrDefault((Func)((TextMeshProUGUI text) => (Object)(object)text != (Object)null && !string.IsNullOrEmpty(((TMP_Text)text).text) && ((TMP_Text)text).text.Trim() == "Blood")); if ((Object)(object)val == (Object)null || (Object)(object)((TMP_Text)val).transform.parent == (Object)null || (Object)(object)((TMP_Text)val).transform.parent.parent == (Object)null) { _log.LogWarning((object)"Gameplay options are not loaded yet; QuickSell settings will appear after the next ScriptEngine reload."); return; } Transform parent = ((TMP_Text)val).transform.parent; Transform parent2 = parent.parent; AddSpacer(parent2, "MassSellSettingsSpacer", 6f); AddToggle(parent2, parent, "QuickSell: Floor Fish", sellFloorItems); AddToggle(parent2, parent, "QuickSell: Inventory Fish/Creatures", sellInventoryItems); AddSpacer(parent2, "MassSellSettingsBottomPadding", 10f); MakeScrollable(parent2); } public void Dispose() { foreach (GameObject nativeSettingsRow in _nativeSettingsRows) { if ((Object)(object)nativeSettingsRow != (Object)null) { Object.Destroy((Object)(object)nativeSettingsRow); } } _nativeSettingsRows.Clear(); } private void AddSpacer(Transform layout, string name, float height) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(LayoutElement) }); val.transform.SetParent(layout, false); LayoutElement component = val.GetComponent(); component.minHeight = height; component.preferredHeight = height; component.flexibleHeight = 0f; _nativeSettingsRows.Add(val); } private void AddToggle(Transform layout, Transform templateRow, string labelText, ConfigEntry setting) { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown GameObject val = Object.Instantiate(((Component)templateRow).gameObject, layout); ((Object)val).name = "MassSell" + labelText.Replace(" ", string.Empty) + "Row"; _nativeSettingsRows.Add(val); TextMeshProUGUI val2 = val.GetComponentsInChildren(true).FirstOrDefault(); if ((Object)(object)val2 != (Object)null) { Behaviour[] components = ((Component)val2).GetComponents(); foreach (Behaviour val3 in components) { if (((object)val3).GetType().Name == "LocalizeStringEvent") { val3.enabled = false; } } ((TMP_Text)val2).text = labelText; } Toggle componentInChildren = val.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { _log.LogError((object)"Native settings toggle template was missing its Toggle component."); Object.Destroy((Object)(object)val); return; } componentInChildren.onValueChanged = new ToggleEvent(); componentInChildren.isOn = setting.Value; ((UnityEvent)(object)componentInChildren.onValueChanged).AddListener((UnityAction)delegate(bool value) { setting.Value = value; }); } private static void MakeScrollable(Transform layout) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00b4: 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_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: 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_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) GameObject val; Transform parent; if ((Object)(object)layout.parent != (Object)null && ((Object)layout.parent).name == "MassSellGameplayViewport") { val = ((Component)layout.parent).gameObject; parent = val.transform.parent; } else { parent = layout.parent; if ((Object)(object)parent == (Object)null) { return; } val = new GameObject("MassSellGameplayViewport", new Type[3] { typeof(RectTransform), typeof(RectMask2D), typeof(Image) }); val.transform.SetParent(parent, false); layout.SetParent(val.transform, false); } if (!((Object)(object)parent == (Object)null)) { RectTransform component = val.GetComponent(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = new Vector2(0f, 8f); component.offsetMax = new Vector2(-18f, -8f); Image obj = val.GetComponent() ?? val.AddComponent(); ((Graphic)obj).color = new Color(0f, 0f, 0f, 0f); ((Graphic)obj).raycastTarget = true; RectTransform val2 = (RectTransform)layout; val2.anchorMin = new Vector2(0f, 1f); val2.anchorMax = new Vector2(1f, 1f); val2.pivot = new Vector2(0.5f, 1f); val2.anchoredPosition = Vector2.zero; val2.sizeDelta = new Vector2(0f, val2.sizeDelta.y); ContentSizeFitter obj2 = ((Component)layout).GetComponent() ?? ((Component)layout).gameObject.AddComponent(); obj2.horizontalFit = (FitMode)0; obj2.verticalFit = (FitMode)2; ScrollRect component2 = ((Component)parent).GetComponent(); if ((Object)(object)component2 != (Object)null) { Object.Destroy((Object)(object)component2); } ScrollRect obj3 = val.GetComponent() ?? val.AddComponent(); obj3.content = val2; obj3.viewport = component; obj3.horizontal = false; obj3.vertical = true; obj3.movementType = (MovementType)2; obj3.scrollSensitivity = 35f; obj3.verticalScrollbar = CreateOrUpdateScrollbar(parent); obj3.verticalScrollbarVisibility = (ScrollbarVisibility)1; obj3.verticalScrollbarSpacing = 4f; Canvas.ForceUpdateCanvases(); LayoutRebuilder.ForceRebuildLayoutImmediate(val2); } } private static Scrollbar CreateOrUpdateScrollbar(Transform display) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) Transform val = display.Find("MassSellGameplayScrollbar"); GameObject val2 = (GameObject)(((Object)(object)val != (Object)null) ? ((object)((Component)val).gameObject) : ((object)new GameObject("MassSellGameplayScrollbar", new Type[3] { typeof(RectTransform), typeof(Image), typeof(Scrollbar) }))); if ((Object)(object)val == (Object)null) { val2.transform.SetParent(display, false); } RectTransform component = val2.GetComponent(); component.anchorMin = new Vector2(1f, 0f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(1f, 0.5f); component.sizeDelta = new Vector2(10f, -16f); component.anchoredPosition = new Vector2(-5f, 0f); Image component2 = val2.GetComponent(); ((Graphic)component2).color = new Color(0f, 0f, 0f, 0.32f); ((Graphic)component2).raycastTarget = true; Transform obj = val2.transform.Find("Sliding Area/Handle"); RectTransform val3 = (RectTransform)(object)((obj is RectTransform) ? obj : null); if ((Object)(object)val3 == (Object)null) { GameObject val4 = new GameObject("Sliding Area", new Type[1] { typeof(RectTransform) }); val4.transform.SetParent(val2.transform, false); RectTransform component3 = val4.GetComponent(); component3.anchorMin = Vector2.zero; component3.anchorMax = Vector2.one; component3.offsetMin = new Vector2(2f, 2f); component3.offsetMax = new Vector2(-2f, -2f); GameObject val5 = new GameObject("Handle", new Type[2] { typeof(RectTransform), typeof(Image) }); val5.transform.SetParent(val4.transform, false); val3 = val5.GetComponent(); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.offsetMin = Vector2.zero; val3.offsetMax = Vector2.zero; } Image component4 = ((Component)val3).GetComponent(); ((Graphic)component4).color = new Color(1f, 1f, 1f, 0.82f); ((Graphic)component4).raycastTarget = true; Scrollbar component5 = val2.GetComponent(); component5.handleRect = val3; ((Selectable)component5).targetGraphic = (Graphic)(object)component4; component5.direction = (Direction)2; return component5; } } internal static class SaleRules { public static bool IsEligibleForSale(Item item, bool onlySellFish) { if ((Object)(object)item == (Object)null || item.IsQuestItem || item.TotalWorth <= 0 || ((Object)(object)item.Creature != (Object)null && !item.Creature.IsDead)) { return false; } if (onlySellFish && !((Object)(object)item.Fish != (Object)null)) { return (Object)(object)item.Creature != (Object)null; } return true; } public static IEnumerable GetFloorFishNearIsland(float waterBuffer, ManualLogSource log) { if ((Object)(object)Island.CurIsland == (Object)null) { log.LogWarning((object)"No island is loaded, so floor-item selling was skipped."); return Enumerable.Empty(); } float num = Mathf.Max(0f, Island.IslandSize + waterBuffer); float rangeSquared = num * num; return ItemManager.Items.Values.Where((Item item) => (Object)(object)item != (Object)null && (Object)(object)item.Fish != (Object)null && ((Creature)item.Fish).IsDead && !item.IsQuestItem && !item.IsInInventory && (Object)(object)item.Holder == (Object)null && (Object)(object)item.SyncedHolder == (Object)null && !item.IsDestroying && !((NetworkBehaviour)item).IsDeinitializing && item.TotalWorth > 0).Where(delegate(Item item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //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) Vector3 val = ((Component)item).transform.position - Island.IslandPos; return ((Vector3)(ref val)).sqrMagnitude <= rangeSquared; }).Distinct() .ToList(); } public static NPC FindMoneyNpc() { return ((IEnumerable)Object.FindObjectsByType()).FirstOrDefault((Func)((NPC npc) => (Object)(object)npc != (Object)null && npc.Quests.Any((NPCQuest quest) => (Object)(object)quest != (Object)null && (int)quest.Type == 1))); } } }