Decompiled source of MinimumQuotaFinder v1.1.4

MinimumQuotaFinder.dll

Decompiled 4 months ago
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 System.Threading;
using BepInEx;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;

[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("MinimumQuotaFinder")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Calculates and highlights the minimum total value of scraps to sell to reach the quota")]
[assembly: AssemblyFileVersion("1.1.4.0")]
[assembly: AssemblyInformationalVersion("1.1.4")]
[assembly: AssemblyProduct("MinimumQuotaFinder")]
[assembly: AssemblyTitle("MinimumQuotaFinder")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.4.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 MinimumQuotaFinder
{
	[HarmonyPatch]
	internal class HUDPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(HUDManager), "Awake")]
		public static void OnAwake(HUDManager __instance)
		{
			int i;
			for (i = 0; i < __instance.controlTipLines.Length && ((TMP_Text)__instance.controlTipLines[i]).text != ""; i++)
			{
			}
			if (i < __instance.controlTipLines.Length)
			{
				((TMP_Text)__instance.controlTipLines[i]).text = "Highlight Minimum Quota : [H]";
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(HUDManager), "PingScan_performed")]
		public static void OnPing(HUDManager __instance, CallbackContext context)
		{
			if (!MinimumQuotaFinder.Instance.CanHighlight(displayReason: false))
			{
				return;
			}
			int i;
			for (i = 0; i < __instance.controlTipLines.Length && ((TMP_Text)__instance.controlTipLines[i]).text != ""; i++)
			{
				if (((TMP_Text)__instance.controlTipLines[i]).text == "Highlight Minimum Quota : [H]")
				{
					return;
				}
			}
			if (i < __instance.controlTipLines.Length)
			{
				((TMP_Text)__instance.controlTipLines[i]).text = "Highlight Minimum Quota : [H]";
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(StartOfRound), "SceneManager_OnLoad")]
		public static void OnChangeLevel(StartOfRound __instance, ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation)
		{
			if (sceneName == "CompanyBuilding")
			{
				MinimumQuotaFinder.Instance.TurnOnHighlight(displayIfCached: true, displayReason: false);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(DepositItemsDesk), "PlaceItemOnCounter")]
		public static void OnItemPlacedOnCounter(DepositItemsDesk __instance, PlayerControllerB playerWhoTriggered)
		{
			if (MinimumQuotaFinder.Instance.IsToggled())
			{
				MinimumQuotaFinder.Instance.TurnOnHighlight(displayIfCached: false, displayReason: false);
			}
		}
	}
	public class MathUtilities
	{
		private const int THRESHOLD = 300000;

		public static List<GrabbableObject> GetGreedyApproximation(List<GrabbableObject> allScrap, int target)
		{
			List<GrabbableObject> list = allScrap.OrderByDescending((GrabbableObject scrap) => scrap.scrapValue).ToList();
			List<GrabbableObject> list2 = new List<GrabbableObject>();
			int num = 0;
			foreach (GrabbableObject item in list)
			{
				int scrapValue = item.scrapValue;
				if (num + scrapValue <= target)
				{
					list2.Add(item);
					num += scrapValue;
					continue;
				}
				break;
			}
			if (list2.Sum((GrabbableObject s) => s.scrapValue) == target)
			{
				return list2;
			}
			for (int num2 = list.Count - 1; num2 >= 0; num2--)
			{
				GrabbableObject val = list[num2];
				if (num + val.scrapValue >= target)
				{
					list2.Add(val);
					break;
				}
			}
			return list2;
		}

		public static IEnumerator GetIncludedCoroutine(List<GrabbableObject> allScrap, bool inverseTarget, int target, int calculationTarget, HashSet<GrabbableObject> includedScrap)
		{
			int numItems = allScrap.Count;
			MemCell[] prev = new MemCell[calculationTarget + 1];
			MemCell[] current = new MemCell[calculationTarget + 1];
			for (int i = 0; i < prev.Length; i++)
			{
				prev[i] = new MemCell(0, new HashSet<GrabbableObject>());
			}
			int earlyTerminationTarget = (inverseTarget ? calculationTarget : target);
			int calculations = 0;
			for (int y = 1; y <= numItems; y++)
			{
				for (int x = 0; x <= calculationTarget; x++)
				{
					int currentScrapValue = allScrap[y - 1].scrapValue;
					if (x < currentScrapValue)
					{
						current[x] = prev[x];
						continue;
					}
					int include = currentScrapValue + prev[x - currentScrapValue].Max;
					int exclude = prev[x].Max;
					if (include > exclude)
					{
						current[x] = new MemCell(include, new HashSet<GrabbableObject>(prev[x - currentScrapValue].Included) { allScrap[y - 1] });
					}
					else
					{
						current[x] = prev[x];
					}
				}
				prev = current;
				current = new MemCell[calculationTarget + 1];
				if (prev[earlyTerminationTarget].Max == earlyTerminationTarget)
				{
					if (inverseTarget)
					{
						includedScrap.UnionWith(allScrap.Where((GrabbableObject scrap) => !prev[earlyTerminationTarget].Included.Contains(scrap)));
					}
					else
					{
						includedScrap.UnionWith(prev[earlyTerminationTarget].Included);
					}
					yield break;
				}
				calculations += calculationTarget;
				if (calculations > 300000)
				{
					yield return null;
					calculations = 0;
				}
			}
			if (inverseTarget)
			{
				includedScrap.UnionWith(allScrap.Where((GrabbableObject scrap) => !prev[calculationTarget].Included.Contains(scrap)));
			}
			else
			{
				for (int j = target; j < prev.Length; j++)
				{
					if (prev[j].Max >= target)
					{
						includedScrap.UnionWith(prev[j].Included);
						break;
					}
				}
			}
			yield return null;
		}
	}
	public class MemCell
	{
		public int Max { get; }

		public HashSet<GrabbableObject> Included { get; }

		public MemCell(int max, HashSet<GrabbableObject> included)
		{
			Max = max;
			Included = included;
		}
	}
	[BepInPlugin("com.github.riceisacereal.MinimumQuotaFinder", "MinimumQuotaFinder", "1.1.4")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class MinimumQuotaFinder : BaseUnityPlugin
	{
		private const string GUID = "com.github.riceisacereal.MinimumQuotaFinder";

		private const string NAME = "MinimumQuotaFinder";

		private const string VERSION = "1.1.4";

		internal static HighlightInputClass InputActionsInstance = new HighlightInputClass();

		public Material wireframeMaterial;

		private bool _toggled = false;

		private int _highlightLockState = 0;

		private int previousQuota = -1;

		private int previousResult = -1;

		private HashSet<GrabbableObject> previousInclude;

		private HashSet<GrabbableObject> previousAllScraps;

		private Dictionary<MeshRenderer, Material[]> _highlightedObjects = new Dictionary<MeshRenderer, Material[]>();

		private List<string> excludedItemNames = new List<string> { "Shotgun", "Ammo", "Gift" };

		public static MinimumQuotaFinder Instance { get; private set; }

		private void Awake()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Instance = this;
			SetupKeybindCallbacks();
			CreateShader();
			Harmony val = new Harmony("com.github.riceisacereal.MinimumQuotaFinder");
			val.PatchAll(Assembly.GetExecutingAssembly());
			((BaseUnityPlugin)this).Logger.LogInfo((object)"MinimumQuotaFinder successfully loaded!");
		}

		private void SetupKeybindCallbacks()
		{
			InputActionsInstance.HighlightKey.performed += OnHighlightKeyPressed;
		}

		private void CreateShader()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			string text = Path.Join((ReadOnlySpan<char>)Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), (ReadOnlySpan<char>)"wireframe");
			AssetBundle val = AssetBundle.LoadFromFile(text);
			Shader val2 = val.LoadAsset<Shader>("assets/wireframeshader.shader");
			wireframeMaterial = new Material(val2);
			val.Unload(false);
		}

		private void OnHighlightKeyPressed(CallbackContext highlightContext)
		{
			if (((CallbackContext)(ref highlightContext)).performed && !((Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null) && HUDManager.Instance.CanPlayerScan() && !(HUDManager.Instance.playerPingingScan > -0.5f))
			{
				if (!_toggled)
				{
					TurnOnHighlight(displayIfCached: true, displayReason: true);
				}
				else
				{
					TurnOffHighlight();
				}
			}
		}

		public void TurnOnHighlight(bool displayIfCached, bool displayReason)
		{
			if (CanHighlight(displayReason))
			{
				((MonoBehaviour)GameNetworkManager.Instance).StartCoroutine(HighlightObjectsCoroutine(displayIfCached));
			}
		}

		public void TurnOffHighlight()
		{
			int num = Interlocked.CompareExchange(ref _highlightLockState, 1, 0);
			if (num == 1)
			{
				return;
			}
			try
			{
				UnhighlightObjects();
			}
			finally
			{
				_toggled = false;
				Interlocked.Exchange(ref _highlightLockState, 0);
			}
		}

		private List<GrabbableObject> GetAllScrap(int level)
		{
			GameObject val = GameObject.Find((level == 3) ? "/Environment" : "/Environment/HangarShip");
			if ((Object)(object)val == (Object)null)
			{
				return new List<GrabbableObject>();
			}
			return (from obj in val.GetComponentsInChildren<GrabbableObject>()
				where obj.itemProperties.isScrap && obj.scrapValue > 0 && ((Component)obj).transform.position.y > -30f && !excludedItemNames.Contains(obj.itemProperties.itemName)
				select obj).ToList();
		}

		private List<GrabbableObject> GetDeskScrap()
		{
			List<GrabbableObject> list = new List<GrabbableObject>();
			DepositItemsDesk val = Object.FindFirstObjectByType<DepositItemsDesk>();
			if ((Object)(object)val != (Object)null)
			{
				list.AddRange(from obj in ((Component)val.deskObjectsContainer).GetComponentsInChildren<GrabbableObject>()
					where obj.itemProperties.isScrap
					select obj);
			}
			return list;
		}

		private int GetDeskScrapValue()
		{
			return GetDeskScrap().Sum((GrabbableObject obj) => obj.scrapValue);
		}

		private IEnumerator HighlightObjectsCoroutine(bool displayIfCached)
		{
			int oldValue = Interlocked.CompareExchange(ref _highlightLockState, 1, 0);
			if (oldValue == 1)
			{
				yield break;
			}
			try
			{
				if (displayIfCached)
				{
					HUDManager.Instance.DisplayTip("MinimumQuotaFinder", "Calculating...", false, false, "LC_Tip1");
				}
				List<GrabbableObject> allScrap = GetAllScrap(StartOfRound.Instance.currentLevelID);
				HashSet<GrabbableObject> toHighlight = new HashSet<GrabbableObject>();
				yield return ((MonoBehaviour)GameNetworkManager.Instance).StartCoroutine(GetSetToHighlight(allScrap, toHighlight, displayIfCached));
				List<GrabbableObject> deskScrap = GetDeskScrap();
				if (toHighlight.Count + deskScrap.Count > 0)
				{
					UnhighlightObjects();
					HighlightObjects(toHighlight);
					HighlightObjects(deskScrap);
					_toggled = true;
				}
				else
				{
					_toggled = false;
				}
			}
			finally
			{
				Interlocked.Exchange(ref _highlightLockState, 0);
			}
		}

		private void HighlightObjects(IEnumerable<GrabbableObject> objectsToHighlight)
		{
			foreach (GrabbableObject item in objectsToHighlight)
			{
				MeshRenderer[] componentsInChildren = ((Component)item).GetComponentsInChildren<MeshRenderer>();
				MeshRenderer[] array = componentsInChildren;
				foreach (MeshRenderer val in array)
				{
					if (!_highlightedObjects.ContainsKey(val))
					{
						_highlightedObjects.Add(val, ((Renderer)val).materials);
						Material[] materials = ((Renderer)val).materials;
						int num = ((materials != null) ? materials.Length : 0);
						Material[] array2 = (Material[])(object)new Material[num];
						Array.Fill(array2, wireframeMaterial);
						((Renderer)val).materials = array2;
					}
				}
			}
		}

		private void UnhighlightObjects()
		{
			foreach (KeyValuePair<MeshRenderer, Material[]> highlightedObject in _highlightedObjects)
			{
				if (!((Object)(object)highlightedObject.Key == (Object)null) && ((Renderer)highlightedObject.Key).materials != null)
				{
					((Renderer)highlightedObject.Key).materials = highlightedObject.Value;
				}
			}
			_highlightedObjects.Clear();
		}

		private IEnumerator GetSetToHighlight(List<GrabbableObject> allScrap, HashSet<GrabbableObject> includedScrap, bool displayIfCached)
		{
			int sold = TimeOfDay.Instance.quotaFulfilled + GetDeskScrapValue();
			int quota = TimeOfDay.Instance.profitQuota;
			if (sold >= quota)
			{
				string colour = ((sold - quota == 0) ? "#A5D971" : "#992403");
				HUDManager.Instance.DisplayTip("MinimumQuotaFinder", $"Quota has been reached (<color={colour}>{sold}</color>/{quota}).", false, false, "LC_Tip1");
				yield break;
			}
			if (allScrap == null || allScrap.Count == 0)
			{
				HUDManager.Instance.DisplayTip("MinimumQuotaFinder", "No scrap detected within the ship.", false, false, "LC_Tip1");
				yield break;
			}
			if (quota == previousQuota && !ThrewAwayIncludedOrSoldExcluded(allScrap, sold) && (SubsetOfPrevious(allScrap) || quota == previousResult))
			{
				includedScrap.UnionWith(previousInclude.Where(allScrap.Contains));
				if (displayIfCached)
				{
					DisplayCalculationResult(includedScrap, sold, quota);
				}
				yield break;
			}
			previousQuota = quota;
			int sumScrapValue = allScrap.Sum((GrabbableObject scrap) => scrap.scrapValue);
			if (sold + sumScrapValue < quota)
			{
				HUDManager.Instance.DisplayTip("MinimumQuotaFinder", $"Not enough scrap to reach quota ({sumScrapValue + sold} < {quota}). Sell everything.", false, false, "LC_Tip1");
				yield break;
			}
			allScrap.Sort((GrabbableObject x, GrabbableObject y) => ((NetworkBehaviour)x).NetworkObjectId.CompareTo(((NetworkBehaviour)y).NetworkObjectId));
			previousAllScraps = allScrap.ToHashSet();
			int target = quota - sold;
			int inverseTarget = allScrap.Sum((GrabbableObject scrap) => scrap.scrapValue) - target;
			if (inverseTarget <= quota)
			{
				yield return ((MonoBehaviour)GameNetworkManager.Instance).StartCoroutine(MathUtilities.GetIncludedCoroutine(allScrap, inverseTarget: true, target, inverseTarget, includedScrap));
			}
			else
			{
				List<GrabbableObject> greedyApproximation = MathUtilities.GetGreedyApproximation(allScrap, quota - sold);
				int greedyTarget = greedyApproximation.Sum((GrabbableObject scrap) => scrap.scrapValue);
				if (greedyTarget == quota - sold)
				{
					includedScrap.UnionWith(greedyApproximation);
				}
				else
				{
					yield return ((MonoBehaviour)GameNetworkManager.Instance).StartCoroutine(MathUtilities.GetIncludedCoroutine(allScrap, inverseTarget: false, target, greedyTarget, includedScrap));
				}
			}
			previousInclude = includedScrap;
			DisplayCalculationResult(includedScrap, sold, quota);
			yield return null;
		}

		private bool ThrewAwayIncludedOrSoldExcluded(List<GrabbableObject> allScrap, int sold)
		{
			if (previousInclude == null)
			{
				return true;
			}
			int num = previousInclude.Intersect(allScrap.ToHashSet()).Sum((GrabbableObject scrap) => scrap.scrapValue);
			return num + sold != previousResult;
		}

		private bool SubsetOfPrevious(List<GrabbableObject> allScrap)
		{
			if (allScrap == null || previousInclude == null || previousAllScraps == null)
			{
				return false;
			}
			return allScrap.ToHashSet().IsSubsetOf(previousAllScraps);
		}

		private void DisplayCalculationResult(IEnumerable<GrabbableObject> toHighlight, int sold, int quota)
		{
			int num = (previousResult = toHighlight.Sum((GrabbableObject scrap) => scrap.scrapValue) + sold);
			int num2 = num - quota;
			string arg = ((num2 == 0) ? "#A5D971" : "#992403");
			HUDManager.Instance.DisplayTip("MinimumQuotaFinder", $"Optimal scrap combination found: {num} ({sold} already sold). " + $"<color={arg}>{num2}</color> over quota. ", false, false, "LC_Tip1");
		}

		public bool IsToggled()
		{
			return _toggled;
		}

		public bool CanHighlight(bool displayReason)
		{
			bool flag = IsOnCompanyMoon(StartOfRound.Instance.currentLevelID);
			if (flag && Math.Abs(StartOfRound.Instance.companyBuyingRate - 1f) >= 0.001f)
			{
				if (displayReason)
				{
					HUDManager.Instance.DisplayTip("MinimumQuotaFinder", "Buying rate is not at 100%, no scrap has been highlighted as the calculations would be inaccurate.", false, false, "LC_Tip1");
				}
				return false;
			}
			if (!flag && !GameNetworkManager.Instance.localPlayerController.isInHangarShipRoom)
			{
				if (displayReason)
				{
					HUDManager.Instance.DisplayTip("MinimumQuotaFinder", "Highlighting disabled, player is not on the ship", false, false, "LC_Tip1");
				}
				return false;
			}
			return true;
		}

		private bool IsOnCompanyMoon(int levelID)
		{
			return ((Object)StartOfRound.Instance.levels[levelID]).name == "CompanyBuildingLevel";
		}
	}
	public class HighlightInputClass : LcInputActions
	{
		[InputAction("<Keyboard>/h", Name = "Toggle scrap highlight")]
		public InputAction HighlightKey { get; set; }
	}
}