Decompiled source of MinimumQuotaFinder v1.1.3

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 TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("MinimumQuotaFinder")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Calculates and highlights the minimum total value of scraps to sell to reach the quota")]
[assembly: AssemblyFileVersion("1.1.3.0")]
[assembly: AssemblyInformationalVersion("1.1.3")]
[assembly: AssemblyProduct("MinimumQuotaFinder")]
[assembly: AssemblyTitle("MinimumQuotaFinder")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.3.0")]
[module: UnverifiableCode]
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, HashSet<GrabbableObject> includedScrap)
	{
		int numItems = allScrap.Count;
		MemCell[] prev = new MemCell[target + 1];
		MemCell[] current = new MemCell[target + 1];
		for (int i = 0; i < prev.Length; i++)
		{
			prev[i] = new MemCell(0, new HashSet<GrabbableObject>());
		}
		int num = 0;
		for (int y = 1; y <= numItems; y++)
		{
			for (int j = 0; j <= target; j++)
			{
				int scrapValue = allScrap[y - 1].scrapValue;
				if (j < scrapValue)
				{
					current[j] = prev[j];
					continue;
				}
				int num2 = scrapValue + prev[j - scrapValue].Max;
				int max = prev[j].Max;
				if (num2 > max)
				{
					HashSet<GrabbableObject> hashSet = new HashSet<GrabbableObject>(prev[j - scrapValue].Included);
					hashSet.Add(allScrap[y - 1]);
					current[j] = new MemCell(num2, hashSet);
				}
				else
				{
					current[j] = prev[j];
				}
			}
			prev = current;
			current = new MemCell[target + 1];
			if (prev[target].Max == target)
			{
				if (inverseTarget)
				{
					includedScrap.UnionWith(allScrap.Where((GrabbableObject scrap) => !prev[target].Included.Contains(scrap)));
				}
				else
				{
					includedScrap.UnionWith(prev[target].Included);
				}
				yield break;
			}
			num += target;
			if (num > 300000)
			{
				yield return null;
				num = 0;
			}
		}
		if (inverseTarget)
		{
			includedScrap.UnionWith(allScrap.Where((GrabbableObject scrap) => !prev[target].Included.Contains(scrap)));
		}
		else
		{
			for (int k = target; k < prev.Length; k++)
			{
				if (prev[k].Max >= target)
				{
					includedScrap.UnionWith(prev[k].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.3")]
[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.3";

	internal static HighlightInputClass InputActionsInstance = new HighlightInputClass();

	public Material wireframeMaterial;

	private bool _toggled;

	private int _highlightLockState;

	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_0017: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		SetupKeybindCallbacks();
		CreateShader();
		new Harmony("com.github.riceisacereal.MinimumQuotaFinder").PatchAll(Assembly.GetExecutingAssembly());
		((BaseUnityPlugin)this).Logger.LogInfo((object)"MinimumQuotaFinder successfully loaded!");
	}

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

	private void CreateShader()
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Expected O, but got Unknown
		AssetBundle obj = AssetBundle.LoadFromFile(Path.Join((ReadOnlySpan<char>)Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), (ReadOnlySpan<char>)"wireframe"));
		Shader val = obj.LoadAsset<Shader>("assets/wireframeshader.shader");
		wireframeMaterial = new Material(val);
		obj.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()
	{
		if (Interlocked.CompareExchange(ref _highlightLockState, 1, 0) == 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.FindObjectOfType<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)
	{
		if (Interlocked.CompareExchange(ref _highlightLockState, 1, 0) == 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>();
			foreach (MeshRenderer val in componentsInChildren)
			{
				if (!_highlightedObjects.ContainsKey(val))
				{
					_highlightedObjects.Add(val, ((Renderer)val).materials);
					Material[] materials = ((Renderer)val).materials;
					Material[] array = (Material[])(object)new Material[(materials != null) ? materials.Length : 0];
					Array.Fill(array, wireframeMaterial);
					((Renderer)val).materials = array;
				}
			}
		}
	}

	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 arg = ((sold - quota == 0) ? "#A5D971" : "#992403");
			HUDManager.Instance.DisplayTip("MinimumQuotaFinder", $"Quota has been reached (<color={arg}>{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 num = allScrap.Sum((GrabbableObject scrap) => scrap.scrapValue);
		if (sold + num < quota)
		{
			HUDManager.Instance.DisplayTip("MinimumQuotaFinder", $"Not enough scrap to reach quota ({num + 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 num2 = allScrap.Sum((GrabbableObject scrap) => scrap.scrapValue) - (quota - sold);
		if (num2 <= quota)
		{
			yield return ((MonoBehaviour)GameNetworkManager.Instance).StartCoroutine(MathUtilities.GetIncludedCoroutine(allScrap, inverseTarget: true, num2, includedScrap));
		}
		else
		{
			List<GrabbableObject> greedyApproximation = MathUtilities.GetGreedyApproximation(allScrap, quota - sold);
			int num3 = greedyApproximation.Sum((GrabbableObject scrap) => scrap.scrapValue);
			if (num3 == quota - sold)
			{
				includedScrap.UnionWith(greedyApproximation);
			}
			else
			{
				yield return ((MonoBehaviour)GameNetworkManager.Instance).StartCoroutine(MathUtilities.GetIncludedCoroutine(allScrap, inverseTarget: false, num3, includedScrap));
			}
		}
		previousInclude = includedScrap;
		DisplayCalculationResult(includedScrap, sold, quota);
		yield return null;
	}

	private bool ThrewAwayIncludedOrSoldExcluded(List<GrabbableObject> allScrap, int sold)
	{
		if (previousInclude == null)
		{
			return true;
		}
		return previousInclude.Intersect(allScrap.ToHashSet()).Sum((GrabbableObject scrap) => scrap.scrapValue) + 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; }
}