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; }
}
}