Decompiled source of SmartSeller v1.0.6

SmartSeller.dll

Decompiled 8 hours ago
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 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", "1.0.6")]
public class SmartSellerPlugin : BaseUnityPlugin
{
	private Harmony harmony;

	private void Awake()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		harmony = new Harmony("lucas.smartseller");
		harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Smart Seller 1.0.6 loaded.");
	}
}
[HarmonyPatch(typeof(Terminal), "QuitTerminal")]
public class TerminalQuitPatch
{
	private static void Postfix()
	{
		TerminalPatch.CancelPendingSellFromTerminalExit();
	}
}
[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
public class TerminalPatch
{
	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 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";
		}
		int result = 0;
		List<GrabbableObject> list2 = new List<GrabbableObject>();
		if (argument == "quota" || argument == "min")
		{
			result = GetQuotaRemaining();
			if (result <= 0)
			{
				return "Quota is already fulfilled.\n\n";
			}
			list2 = FindBestCombination(list, result, currentSellMultiplier, preferAtLeastTarget: true);
		}
		else if (argument == "max")
		{
			list2 = list;
			result = CalculateTotal(list2, 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";
			}
			list2 = FindBestCombination(list, result, currentSellMultiplier, preferAtLeastTarget: false);
		}
		if (list2.Count == 0)
		{
			return "Could not find any matching scrap.\n\n";
		}
		int num = CalculateTotal(list2, currentSellMultiplier);
		SetPendingSell(list2, result, num, argument);
		string text = "";
		foreach (GrabbableObject item in list2)
		{
			int num2 = Mathf.RoundToInt((float)item.scrapValue * currentSellMultiplier);
			string text2 = "Unknown item";
			if ((Object)(object)item.itemProperties != (Object)null)
			{
				text2 = item.itemProperties.itemName;
			}
			text = text + "Selected: " + text2 + " ($" + num2 + ")\n";
		}
		text = text + "\nTarget: $" + result + " | Selected total: $" + num + "\n";
		text += BuildOvertimePreview(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);
		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 i = 0; i < pendingSellItems.Count; i++)
			{
				GrabbableObject item = pendingSellItems[i];
				PrepareItemForCompanyDesk(item, val, i);
				if (!val.itemsOnCounter.Contains(item))
				{
					val.itemsOnCounter.Add(item);
				}
			}
			int sellValue = CalculateTotal(pendingSellItems, GetCurrentSellMultiplier());
			int count = pendingSellItems.Count;
			string text = BuildOvertimePreview(sellValue);
			val.SellItemsOnServer();
			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 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 BuildOvertimePreview(int sellValue)
	{
		try
		{
			int quotaFulfilled = GetQuotaFulfilled();
			int profitQuota = GetProfitQuota();
			int daysUntilDeadline = GetDaysUntilDeadline();
			int num = quotaFulfilled + sellValue;
			int num2 = num - profitQuota;
			if (profitQuota <= 0)
			{
				return "Overtime preview unavailable.\n";
			}
			if (num2 <= 0)
			{
				int num3 = profitQuota - num;
				if (num3 < 0)
				{
					num3 = 0;
				}
				return "Overtime bonus preview: $0\nQuota after sell: $" + num + " / $" + profitQuota + "\nStill needed for quota: $" + num3 + "\n";
			}
			int num4 = CalculateOvertimeBonus(num, profitQuota, daysUntilDeadline);
			return "Overtime bonus preview: $" + num4 + "\nQuota after sell: $" + num + " / $" + profitQuota + "\nOver quota by: $" + num2 + "\nDays left bonus used: " + daysUntilDeadline + " day(s)\n";
		}
		catch
		{
			return "Overtime preview unavailable.\n";
		}
	}

	private static int CalculateOvertimeBonus(int quotaFulfilledAfterSell, int profitQuota, int daysUntilDeadline)
	{
		int num = quotaFulfilledAfterSell - profitQuota;
		if (num <= 0)
		{
			return 0;
		}
		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<GrabbableObject> result = new List<GrabbableObject>();
		int count = items.Count;
		if (count > 20)
		{
			items = items.OrderByDescending((GrabbableObject x) => x.scrapValue).Take(20).ToList();
			count = items.Count;
		}
		if (preferAtLeastTarget)
		{
			List<GrabbableObject> list = new List<GrabbableObject>();
			List<GrabbableObject> result2 = new List<GrabbableObject>();
			int num = int.MaxValue;
			int num2 = -1;
			int num3 = 1 << count;
			for (int i = 1; i < num3; i++)
			{
				List<GrabbableObject> list2 = new List<GrabbableObject>();
				int num4 = 0;
				for (int j = 0; j < count; j++)
				{
					if ((i & (1 << j)) != 0)
					{
						list2.Add(items[j]);
						num4 += Mathf.RoundToInt((float)items[j].scrapValue * multiplier);
					}
				}
				if (num4 >= target)
				{
					if (num4 < num)
					{
						num = num4;
						list = list2;
					}
				}
				else if (num4 > num2)
				{
					num2 = num4;
					result2 = list2;
				}
			}
			if (list.Count > 0)
			{
				return list;
			}
			return result2;
		}
		int num5 = int.MaxValue;
		int num6 = 1 << count;
		for (int k = 1; k < num6; k++)
		{
			List<GrabbableObject> list3 = new List<GrabbableObject>();
			int num7 = 0;
			for (int l = 0; l < count; l++)
			{
				if ((k & (1 << l)) != 0)
				{
					list3.Add(items[l]);
					num7 += Mathf.RoundToInt((float)items[l].scrapValue * multiplier);
				}
			}
			int num8 = Math.Abs(target - num7);
			if (num8 < num5)
			{
				num5 = num8;
				result = list3;
				if (num8 == 0)
				{
					break;
				}
			}
		}
		return result;
	}
}