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 SmartSeller v2.0.0
SmartSeller.dll
Decompiled 13 hours agousing 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 HarmonyLib; using TMPro; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SmartSeller")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SmartSeller")] [assembly: AssemblyTitle("SmartSeller")] [assembly: AssemblyVersion("1.0.0.0")] namespace SmartSeller; [BepInPlugin("lucas.smartseller", "Smart Seller", "2.0.0")] public class SmartSellerPlugin : BaseUnityPlugin { private readonly Harmony harmony = new Harmony("lucas.smartseller"); internal static Func<bool> ShouldProtectUtilityItems = () => true; private void Awake() { ConfigEntry<bool> protectUtilityItemsConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Whitelist", "WhitelistUtilityItems", true, "When enabled, Smart Seller will ignore kitchen knives, shotguns, shotgun shells, and keys."); ShouldProtectUtilityItems = () => protectUtilityItemsConfig.Value; harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Smart Seller 1.0.10 loaded."); } } [HarmonyPatch(typeof(Terminal), "QuitTerminal")] public class TerminalQuitPatch { private static void Postfix() { TerminalPatch.CancelPendingSellFromTerminalExit(); } } [HarmonyPatch(typeof(Terminal), "LoadNewNode")] public class TerminalHelpPatch { private static void Prefix(TerminalNode __0) { TerminalPatch.TryAppendSmartSellerHelpToNode(__0); } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] public class TerminalPatch { private class ItemValue { public GrabbableObject Item; public int EffectiveValue; public int RawValue; } private class CombinationResult { public List<GrabbableObject> Items = new List<GrabbableObject>(); public int Total; public int RawTotal; } private static List<GrabbableObject> pendingSellItems = new List<GrabbableObject>(); private static int pendingSellTarget = 0; private static int pendingSellTotal = 0; private static string pendingSellMode = ""; private static bool Prefix(Terminal __instance, ref TerminalNode __result, int ___textAdded, TMP_InputField ___screenText) { string text; try { if (___screenText == null) { return true; } if (___textAdded <= 0) { return true; } int num = ___screenText.text.Length - ___textAdded; if (num < 0) { return true; } text = ___screenText.text.Substring(num).Trim().ToLower(); } catch { return true; } if (HasPendingSell()) { if (text == "c" || text == "confirm") { __result = CreateTerminalNode(ConfirmPendingSell()); return false; } if (text == "d" || text == "deny" || text == "cancel") { __result = CreateTerminalNode(DenyPendingSell()); return false; } __result = CreateTerminalNode("Pending sell active.\nPress C to confirm sell or D to deny sell.\n\n"); return false; } if (text == "sell" || text.StartsWith("sell ")) { string message = HandleSellCommand(text); __result = CreateTerminalNode(message); return false; } return true; } public static void TryAppendSmartSellerHelpToNode(TerminalNode node) { try { if (node != null && !string.IsNullOrEmpty(node.displayText)) { string displayText = node.displayText; string text = displayText.ToLower(); if (!text.Contains("smart seller") && text.Contains("store") && text.Contains("moons") && text.Contains("bestiary") && text.Contains("storage")) { string text2 = "\n\n>SMART SELLER\nsell <quota>, sell <max>, sell <value>\nExamples: sell quota, sell max, sell 500\n"; node.displayText = displayText.TrimEnd() + text2; } } } catch { } } public static void CancelPendingSellFromTerminalExit() { if (HasPendingSell()) { ClearPendingSell(); } } private static string HandleSellCommand(string text) { if (!IsAtCompany()) { return "You must be at the Company Building.\n\n"; } string[] array = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { return "Usage: sell <amount>, sell quota, sell min, or sell max\n\n"; } string argument = array[1].ToLower(); return PrepareSell(argument); } private static TerminalNode CreateTerminalNode(string message) { TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); val.displayText = message; val.clearPreviousText = true; return val; } private static bool IsAtCompany() { try { if ((Object)(object)StartOfRound.Instance == (Object)null) { return false; } if ((Object)(object)StartOfRound.Instance.currentLevel == (Object)null) { return false; } SelectableLevel currentLevel = StartOfRound.Instance.currentLevel; if (currentLevel.levelID == 3) { return true; } string planetName = currentLevel.PlanetName; if (!string.IsNullOrEmpty(planetName)) { string text = planetName.ToLower(); if (text.Contains("company")) { return true; } if (text.Contains("gordion")) { return true; } } return false; } catch { return false; } } private static string PrepareSell(string argument) { float currentSellMultiplier = GetCurrentSellMultiplier(); List<GrabbableObject> list = (from x in Object.FindObjectsOfType<GrabbableObject>() where (Object)(object)x != (Object)null && x.isInShipRoom && x.scrapValue > 0 && !x.isHeld select x).ToList(); if (list.Count == 0) { return "No scrap found.\n\n"; } List<GrabbableObject> list2 = list.Where((GrabbableObject x) => !IsProtectedUtilityItem(x)).ToList(); if (list2.Count == 0) { if (SmartSellerPlugin.ShouldProtectUtilityItems()) { return "No eligible scrap found.\nUtility item whitelist is enabled, so knives, shotguns, shells, and keys were ignored.\n\n"; } return "No eligible scrap found.\n\n"; } int result = 0; List<GrabbableObject> list3 = new List<GrabbableObject>(); if (argument == "quota" || argument == "min") { result = GetQuotaRemaining(); if (result <= 0) { return "Quota is already fulfilled.\n\n"; } list3 = FindBestCombination(list2, result, currentSellMultiplier, preferAtLeastTarget: true); } else if (argument == "max") { list3 = list2; result = CalculateTotal(list3, currentSellMultiplier); } else { if (!int.TryParse(argument, out result)) { return "Invalid command. Use: sell <amount>, sell quota, sell min, or sell max\n\n"; } if (result <= 0) { return "Sell amount must be higher than 0.\n\n"; } list3 = FindBestCombination(list2, result, currentSellMultiplier, preferAtLeastTarget: false); } if (list3.Count == 0) { return "Could not find any matching scrap.\n\n"; } int num = CalculateTotal(list3, currentSellMultiplier); SetPendingSell(list3, result, num, argument); string text = ""; foreach (GrabbableObject item in list3) { int num2 = Mathf.RoundToInt((float)item.scrapValue * currentSellMultiplier); string itemName = GetItemName(item); text = text + "Selected: " + itemName + " ($" + num2 + ")\n"; } text = text + "\nTarget: $" + result + " | Selected total: $" + num + "\n"; text += BuildProfitPreview(num); return text + "\nC to confirm sell | D to deny sell\n\n"; } private static string ConfirmPendingSell() { if (!HasPendingSell()) { return "No pending sell to confirm.\n\n"; } if (!IsAtCompany()) { ClearPendingSell(); return "Sell cancelled. You are no longer at the Company Building.\n\n"; } if ((Object)(object)NetworkManager.Singleton == (Object)null) { return "Could not access NetworkManager. Sell was not confirmed.\n\n"; } if (!NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer) { return "Only the host can confirm automatic selling in this version.\n\n"; } DepositItemsDesk val = Object.FindObjectOfType<DepositItemsDesk>(); if ((Object)(object)val == (Object)null) { return "Could not find the Company sell desk. Sell was not confirmed.\n\n"; } pendingSellItems.RemoveAll((GrabbableObject x) => (Object)(object)x == (Object)null || x.scrapValue <= 0 || x.isHeld || IsProtectedUtilityItem(x)); if (pendingSellItems.Count == 0) { ClearPendingSell(); return "Pending sell expired. No valid scrap left to sell.\n\n"; } try { if (val.itemsOnCounter == null) { return "Could not access the Company counter list. Sell was not confirmed.\n\n"; } val.itemsOnCounter.RemoveAll((GrabbableObject x) => (Object)(object)x == (Object)null); if (val.itemsOnCounter.Count > 0) { return "The Company counter already has items on it. Clear the counter first, then confirm again.\n\n"; } for (int num = 0; num < pendingSellItems.Count; num++) { GrabbableObject item = pendingSellItems[num]; PrepareItemForCompanyDesk(item, val, num); if (!val.itemsOnCounter.Contains(item)) { val.itemsOnCounter.Add(item); } } List<GrabbableObject> list = new List<GrabbableObject>(pendingSellItems); int sellValue = CalculateTotal(list, GetCurrentSellMultiplier()); int count = list.Count; string text = BuildProfitPreview(sellValue); val.SellItemsOnServer(); RemoveSoldItemsFromWorld(list); val.itemsOnCounter.RemoveAll((GrabbableObject x) => (Object)(object)x == (Object)null); ClearPendingSell(); return "Confirmed sell.\nSold " + count + " selected item(s).\nTotal value: $" + sellValue + "\n" + text + "\n"; } catch (Exception ex) { return "Sell failed before confirmation finished.\nError: " + ex.Message + "\n\n"; } } private static string DenyPendingSell() { ClearPendingSell(); return "Sell denied. Returning to terminal.\n\n"; } private static void PrepareItemForCompanyDesk(GrabbableObject item, DepositItemsDesk desk, int index) { if (!((Object)(object)item == (Object)null)) { item.isInShipRoom = false; item.isInElevator = false; item.hasHitGround = true; ((Component)item).gameObject.SetActive(true); } } private static void RemoveSoldItemsFromWorld(List<GrabbableObject> soldItems) { if (soldItems == null) { return; } for (int i = 0; i < soldItems.Count; i++) { GrabbableObject val = soldItems[i]; if ((Object)(object)val == (Object)null) { continue; } try { val.isInShipRoom = false; val.isInElevator = false; val.scrapValue = 0; val.SetScrapValue(0); } catch { } try { NetworkObject component = ((Component)val).GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null && component.IsSpawned) { component.Despawn(true); continue; } } catch { } try { ((Component)val).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)val).gameObject); } catch { } } } private static bool HasPendingSell() { return pendingSellItems != null && pendingSellItems.Count > 0; } private static void SetPendingSell(List<GrabbableObject> items, int target, int total, string mode) { pendingSellItems = new List<GrabbableObject>(items); pendingSellTarget = target; pendingSellTotal = total; pendingSellMode = mode; } private static void ClearPendingSell() { pendingSellItems.Clear(); pendingSellTarget = 0; pendingSellTotal = 0; pendingSellMode = ""; } private static int GetQuotaRemaining() { try { int num = TimeOfDay.Instance.profitQuota - TimeOfDay.Instance.quotaFulfilled; if (num < 0) { num = 0; } return num; } catch { return 0; } } private static int GetQuotaFulfilled() { try { return TimeOfDay.Instance.quotaFulfilled; } catch { return 0; } } private static int GetProfitQuota() { try { return TimeOfDay.Instance.profitQuota; } catch { return 0; } } private static int GetDaysUntilDeadline() { try { return TimeOfDay.Instance.daysUntilDeadline; } catch { return 0; } } private static string BuildProfitPreview(int sellValue) { try { int quotaFulfilled = GetQuotaFulfilled(); int profitQuota = GetProfitQuota(); int daysUntilDeadline = GetDaysUntilDeadline(); if (profitQuota <= 0) { return "Profit bonus preview unavailable.\n"; } int num = quotaFulfilled + sellValue; int num2 = CalculateProfitBonus(quotaFulfilled, profitQuota, daysUntilDeadline); int num3 = CalculateProfitBonus(num, profitQuota, daysUntilDeadline); int num4 = num3 - num2; if (num4 < 0) { num4 = 0; } int num5 = sellValue + num4; int num6 = num + num3; string text = ""; text += "\nProfit quota preview:\n"; text = text + "Quota after sell: $" + num + " / $" + profitQuota + "\n"; if (num < profitQuota) { text = text + "Still needed for quota: $" + (profitQuota - num) + "\n"; text += "Profit bonus after sell: $0\n"; return text + "Expected value from this sell: $" + sellValue + "\n"; } int num7 = num - profitQuota; int value = 15 * daysUntilDeadline; text = text + "Over quota by: $" + num7 + "\n"; text = text + "Day efficiency modifier: " + FormatSignedMoney(value) + "\n"; text = text + "Profit bonus after sell: $" + num3 + "\n"; text = text + "Bonus added by this sell: +" + num4 + "\n"; text = text + "Expected value from this sell: $" + sellValue + " + $" + num4 + " = $" + num5 + "\n"; return text + "Total quota profit after sell: $" + num6 + "\n"; } catch { return "Profit bonus preview unavailable.\n"; } } private static string FormatSignedMoney(int value) { if (value > 0) { return "+$" + value; } if (value < 0) { return "-$" + Math.Abs(value); } return "$0"; } private static int CalculateProfitBonus(int quotaFulfilledValue, int profitQuota, int daysUntilDeadline) { if (quotaFulfilledValue < profitQuota) { return 0; } int num = quotaFulfilledValue - profitQuota; float num2 = (float)num / 5f + 15f * (float)daysUntilDeadline; int num3 = Mathf.FloorToInt(num2); if (num3 < 0) { num3 = 0; } return num3; } private static float GetCurrentSellMultiplier() { try { if ((Object)(object)StartOfRound.Instance != (Object)null) { return StartOfRound.Instance.companyBuyingRate; } } catch { } try { return TimeOfDay.Instance.daysUntilDeadline switch { 0 => 1f, 1 => 0.77f, 2 => 0.55f, _ => 0.33f, }; } catch { return 1f; } } private static int CalculateTotal(List<GrabbableObject> items, float multiplier) { int num = 0; for (int i = 0; i < items.Count; i++) { if (!((Object)(object)items[i] == (Object)null)) { num += Mathf.RoundToInt((float)items[i].scrapValue * multiplier); } } return num; } private static List<GrabbableObject> FindBestCombination(List<GrabbableObject> items, int target, float multiplier, bool preferAtLeastTarget) { List<ItemValue> list = new List<ItemValue>(); for (int i = 0; i < items.Count; i++) { GrabbableObject val = items[i]; if (!((Object)(object)val == (Object)null)) { int num = Mathf.RoundToInt((float)val.scrapValue * multiplier); if (num > 0) { ItemValue itemValue = new ItemValue(); itemValue.Item = val; itemValue.EffectiveValue = num; itemValue.RawValue = val.scrapValue; list.Add(itemValue); } } } list = (from x in list orderby x.EffectiveValue, GetItemName(x.Item) select x).ToList(); if (list.Count == 0) { return new List<GrabbableObject>(); } int num2 = 0; int num3 = 0; for (int num4 = 0; num4 < list.Count; num4++) { num2 += list[num4].EffectiveValue; if (list[num4].EffectiveValue > num3) { num3 = list[num4].EffectiveValue; } } int num5 = num2; if (target > 0 && num3 > 0) { num5 = Math.Min(num2, target + num3); } Dictionary<int, CombinationResult> dictionary = new Dictionary<int, CombinationResult>(); CombinationResult combinationResult = new CombinationResult(); combinationResult.Total = 0; combinationResult.RawTotal = 0; dictionary[0] = combinationResult; for (int num6 = 0; num6 < list.Count; num6++) { ItemValue itemValue2 = list[num6]; List<KeyValuePair<int, CombinationResult>> list2 = dictionary.ToList(); for (int num7 = 0; num7 < list2.Count; num7++) { int key = list2[num7].Key; CombinationResult value = list2[num7].Value; int num8 = key + itemValue2.EffectiveValue; if (num8 <= num5) { CombinationResult combinationResult2 = new CombinationResult(); combinationResult2.Total = num8; combinationResult2.RawTotal = value.RawTotal + itemValue2.RawValue; combinationResult2.Items = new List<GrabbableObject>(value.Items); combinationResult2.Items.Add(itemValue2.Item); if (!dictionary.TryGetValue(num8, out var value2)) { dictionary[num8] = combinationResult2; } else if (IsBetterSameTotalCombination(combinationResult2, value2)) { dictionary[num8] = combinationResult2; } } } } CombinationResult combinationResult3 = null; foreach (CombinationResult value3 in dictionary.Values) { if (value3 != null && value3.Total > 0 && (combinationResult3 == null || IsBetterCandidate(value3, combinationResult3, target, preferAtLeastTarget))) { combinationResult3 = value3; } } if (combinationResult3 == null) { return new List<GrabbableObject>(); } return combinationResult3.Items; } private static bool IsBetterSameTotalCombination(CombinationResult challenger, CombinationResult current) { if (challenger.Items.Count != current.Items.Count) { return challenger.Items.Count < current.Items.Count; } if (challenger.RawTotal != current.RawTotal) { return challenger.RawTotal < current.RawTotal; } return false; } private static bool IsBetterCandidate(CombinationResult challenger, CombinationResult current, int target, bool preferAtLeastTarget) { if (preferAtLeastTarget) { bool flag = challenger.Total >= target; bool flag2 = current.Total >= target; if (flag && !flag2) { return true; } if (!flag && flag2) { return false; } if (flag && flag2) { if (challenger.Total != current.Total) { return challenger.Total < current.Total; } } else if (challenger.Total != current.Total) { return challenger.Total > current.Total; } return IsBetterSameTotalCombination(challenger, current); } int num = Math.Abs(target - challenger.Total); int num2 = Math.Abs(target - current.Total); if (num != num2) { return num < num2; } if (challenger.Total != current.Total) { return challenger.Total < current.Total; } return IsBetterSameTotalCombination(challenger, current); } private static bool IsProtectedUtilityItem(GrabbableObject item) { try { if (!SmartSellerPlugin.ShouldProtectUtilityItems()) { return false; } string text = GetItemName(item).ToLower().Replace("-", " ").Replace("_", " ") .Trim(); if (string.IsNullOrEmpty(text)) { return false; } if (text.Contains("kitchen") && text.Contains("knife")) { return true; } if (text == "knife" || text.Contains(" knife") || text.Contains("knife ")) { return true; } if (text.Contains("shotgun")) { return true; } if (text.Contains("shell") && text.Contains("shotgun")) { return true; } if (text == "key" || text.EndsWith(" key") || text.Contains(" key ")) { return true; } return false; } catch { return false; } } private static string GetItemName(GrabbableObject item) { try { if ((Object)(object)item != (Object)null && (Object)(object)item.itemProperties != (Object)null && !string.IsNullOrEmpty(item.itemProperties.itemName)) { return item.itemProperties.itemName; } } catch { } return "Unknown item"; } }