Decompiled source of Josh MassScan v0.1.6

plugins/JoshMassScan/JoshMassScan.dll

Decompiled 7 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 BepInEx.Configuration;
using HarmonyLib;
using PropStreaming;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("JoshMassScan")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("JoshMassScan")]
[assembly: AssemblyTitle("JoshMassScan")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace JoshMassScan;

[BepInPlugin("Josh.Techtonica.MassScan", "Josh MassScan", "0.1.6")]
public sealed class JoshMassScanPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "Josh.Techtonica.MassScan";

	public const string PluginName = "Josh MassScan";

	public const string PluginVersion = "0.1.6";

	private static readonly MethodInfo SetScanEffectMethod = AccessTools.Method(typeof(ScannableData), "SetScanEffect", (Type[])null, (Type[])null);

	private readonly Queue<InstanceLookup> scanQueue = new Queue<InstanceLookup>();

	private readonly HashSet<InstanceLookup> queuedLookups = new HashSet<InstanceLookup>();

	private readonly Dictionary<InstanceLookup, ScannableData> previewTargets = new Dictionary<InstanceLookup, ScannableData>();

	private ConfigEntry<int> maxSelection;

	private ConfigEntry<float> scanSpeedMultiplier;

	private ConfigEntry<bool> verboseLogging;

	private bool hasCurrentTarget;

	private bool currentTargetWasScanning;

	private bool scannerOwnsEraseMode;

	private bool scannerWasSelectedInConstruction;

	private bool observedConstructionMode;

	private ConstructionMode previousConstructionMode;

	private int lastPreviewCount = -1;

	private int lastTickFrame = -1;

	private bool loggedRuntimeTick;

	private bool loggedBuilderPatch;

	private bool loggedReadinessFailure;

	private InstanceLookup currentLookup;

	private ScannableData currentScanData;

	internal static JoshMassScanPlugin Instance { get; private set; }

	private void Awake()
	{
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		maxSelection = ((BaseUnityPlugin)this).Config.Bind<int>("Safety", "MaximumSelection", 100, "Maximum number of objects that one mass-scan selection can queue.");
		scanSpeedMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Scanning", "ScanSpeedMultiplier", 1f, "Multiplier applied to each object's normal scan speed.");
		verboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", true, "Log every queued and completed object while testing.");
		Harmony val = new Harmony("Josh.Techtonica.MassScan");
		val.PatchAll(typeof(JoshMassScanPlugin).Assembly);
		int num = val.GetPatchedMethods().Count();
		((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("{0} {1} loaded. Maximum selection: {2}. ", "Josh MassScan", "0.1.6", maxSelection.Value) + $"Patched methods: {num}."));
	}

	private void OnDestroy()
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		ClearPreview();
		CancelQueue("Plugin unloaded");
		new Harmony("Josh.Techtonica.MassScan").UnpatchSelf();
		Instance = null;
	}

	private void Update()
	{
		Tick(((Object)(object)Player.instance != (Object)null) ? Player.instance.builder : null);
	}

	internal void Tick(PlayerBuilder builder)
	{
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0149: Unknown result type (might be due to invalid IL or missing references)
		//IL_0160: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_0201: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_016e: Invalid comparison between Unknown and I4
		//IL_0191: Unknown result type (might be due to invalid IL or missing references)
		//IL_0193: Invalid comparison between Unknown and I4
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
		if (Time.frameCount == lastTickFrame)
		{
			return;
		}
		if (!IsGameReady() || (Object)(object)builder == (Object)null)
		{
			if (!loggedReadinessFailure && (Object)(object)builder != (Object)null)
			{
				loggedReadinessFailure = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Runtime tick waiting for readiness: " + $"player={(Object)(object)Player.instance != (Object)null}, " + $"toolbar={(Object)(object)Player.instance != (Object)null && (Object)(object)Player.instance.toolbar != (Object)null}, " + $"builder={(Object)(object)Player.instance != (Object)null && (Object)(object)Player.instance.builder != (Object)null}, " + $"input={(Object)(object)InputHandler.instance != (Object)null}, " + $"props={(Object)(object)PropManager.instance != (Object)null}."));
			}
			return;
		}
		lastTickFrame = Time.frameCount;
		if (!loggedRuntimeTick)
		{
			loggedRuntimeTick = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Runtime tick active through PlayerBuilder.Update.");
		}
		ConstructionMode currentConstructionMode = builder.CurrentConstructionMode;
		if (!observedConstructionMode)
		{
			previousConstructionMode = currentConstructionMode;
			observedConstructionMode = true;
		}
		if (currentConstructionMode != previousConstructionMode)
		{
			if ((int)currentConstructionMode == 1 && scannerWasSelectedInConstruction)
			{
				scannerOwnsEraseMode = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Scanner mode entered from a scanner-equipped construction state.");
			}
			else if ((int)currentConstructionMode != 1 && scannerOwnsEraseMode)
			{
				scannerOwnsEraseMode = false;
				ClearPreview();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Scanner mode exited.");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)($"Construction mode changed from {previousConstructionMode} to {currentConstructionMode}; " + $"scanner ownership={scannerOwnsEraseMode}."));
			previousConstructionMode = currentConstructionMode;
		}
		if ((int)currentConstructionMode == 0)
		{
			scannerWasSelectedInConstruction = Player.instance.toolbar.selectedInfo is ScannerInfo;
		}
		if (InputHandler.instance.PutAwayPressed && (hasCurrentTarget || scanQueue.Count > 0))
		{
			CancelQueue("Cancelled by player");
		}
		else
		{
			ProcessScanQueue();
		}
	}

	internal void ConfirmRuntimePatch(string source)
	{
		if (!loggedBuilderPatch)
		{
			loggedBuilderPatch = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Runtime Harmony hook reached through " + source + "."));
		}
	}

	internal static bool IsScannerMode(PlayerBuilder builder = null)
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Invalid comparison between Unknown and I4
		if (!IsGameReady())
		{
			return false;
		}
		if (builder == null)
		{
			builder = Player.instance.builder;
		}
		if ((Object)(object)builder != (Object)null && (int)builder.CurrentConstructionMode == 1 && (Object)(object)Instance != (Object)null)
		{
			return Instance.scannerOwnsEraseMode;
		}
		return false;
	}

	internal void ObserveModeInput(PlayerBuilder builder)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Invalid comparison between Unknown and I4
		if (IsGameReady() && InputHandler.instance.EraseToggle)
		{
			if ((int)builder.CurrentConstructionMode == 0 && (Player.instance.toolbar.selectedInfo is ScannerInfo || scannerWasSelectedInConstruction))
			{
				scannerOwnsEraseMode = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Scanner mode requested with the scanner equipped.");
			}
			else if ((int)builder.CurrentConstructionMode == 1 && scannerOwnsEraseMode)
			{
				scannerOwnsEraseMode = false;
				ClearPreview();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Scanner mode cancelled.");
			}
		}
	}

	private static bool IsGameReady()
	{
		if ((Object)(object)Player.instance != (Object)null && (Object)(object)Player.instance.toolbar != (Object)null && (Object)(object)Player.instance.builder != (Object)null && (Object)(object)InputHandler.instance != (Object)null)
		{
			return (Object)(object)PropManager.instance != (Object)null;
		}
		return false;
	}

	internal void RefreshPreview(Bounds bounds)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_0127: Unknown result type (might be due to invalid IL or missing references)
		if (!IsScannerMode())
		{
			ClearPreview();
			return;
		}
		Dictionary<InstanceLookup, ScannableData> dictionary = FindEligibleTargets(bounds, maxSelection.Value);
		if (dictionary.Count != lastPreviewCount)
		{
			lastPreviewCount = dictionary.Count;
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Scanner box contains {dictionary.Count} eligible object(s).");
		}
		foreach (KeyValuePair<InstanceLookup, ScannableData> previewTarget in previewTargets)
		{
			if (!dictionary.ContainsKey(previewTarget.Key))
			{
				SetScanEffect(previewTarget.Value, previewTarget.Key, enabled: false);
			}
		}
		foreach (KeyValuePair<InstanceLookup, ScannableData> item in dictionary)
		{
			if (!previewTargets.ContainsKey(item.Key))
			{
				SetScanEffect(item.Value, item.Key, enabled: true);
			}
		}
		previewTargets.Clear();
		foreach (KeyValuePair<InstanceLookup, ScannableData> item2 in dictionary)
		{
			previewTargets.Add(item2.Key, item2.Value);
		}
	}

	internal void CommitSelection(Bounds bounds)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<InstanceLookup, ScannableData> dictionary = FindEligibleTargets(bounds, maxSelection.Value);
		ClearPreview();
		int num = 0;
		foreach (InstanceLookup key in dictionary.Keys)
		{
			if (queuedLookups.Add(key))
			{
				scanQueue.Enqueue(key);
				num++;
				if (verboseLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Queued scan target " + FormatLookup(key) + "."));
				}
			}
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)$"Scanner box queued {num} object(s); {scanQueue.Count} waiting.");
	}

	internal void ClearPreview()
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		foreach (KeyValuePair<InstanceLookup, ScannableData> previewTarget in previewTargets)
		{
			SetScanEffect(previewTarget.Value, previewTarget.Key, enabled: false);
		}
		previewTargets.Clear();
		lastPreviewCount = -1;
	}

	private Dictionary<InstanceLookup, ScannableData> FindEligibleTargets(Bounds bounds, int limit)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<InstanceLookup, ScannableData> dictionary = new Dictionary<InstanceLookup, ScannableData>();
		if (IsGameReady())
		{
			Vector3 size = ((Bounds)(ref bounds)).size;
			if (!(((Vector3)(ref size)).sqrMagnitude <= 0.001f) && limit > 0)
			{
				byte curStrataNum = VoxelManager.curStrataNum;
				Collider[] array = Physics.OverlapBox(((Bounds)(ref bounds)).center, ((Bounds)(ref bounds)).extents, Quaternion.identity, -1, (QueryTriggerInteraction)2);
				int num = default(int);
				PropState val3 = default(PropState);
				foreach (Collider val in array)
				{
					Frobbable val2 = ((Component)val).GetComponent<Frobbable>() ?? ((Component)val).GetComponentInParent<Frobbable>();
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					InstanceLookup lookup = val2.lookup;
					if (dictionary.ContainsKey(lookup) || queuedLookups.Contains(lookup) || (hasCurrentTarget && ((InstanceLookup)(ref lookup)).Equals(currentLookup)))
					{
						continue;
					}
					FrillInformation frillInformation = val2.GetFrillInformation();
					ScannableData scannableData = frillInformation.scannableData;
					if (frillInformation.isScannable && !((Object)(object)scannableData == (Object)null) && !scannableData.IsAlreadyScanned(lookup) && PropManager.instance.propLookUpToArrayIndex.TryGetValue(lookup, ref num) && PropManager.instance.propStrata[num] == curStrataNum && PropManager.instance.GetPropState(ref lookup, ref val3) && val3.isAlive)
					{
						dictionary.Add(lookup, scannableData);
						if (dictionary.Count >= limit)
						{
							break;
						}
					}
				}
				return dictionary;
			}
		}
		return dictionary;
	}

	private void ProcessScanQueue()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		if (!hasCurrentTarget && !TryStartNextTarget())
		{
			return;
		}
		if (!IsTargetEligible(currentLookup, currentScanData, out var state))
		{
			FinishCurrentTarget("Skipped invalid or completed target");
			return;
		}
		currentScanData.Scan(currentLookup, true, ref currentTargetWasScanning);
		float num = Mathf.Max(0.01f, currentScanData.GetScanDuration());
		float num2 = Mathf.Max(0.01f, scanSpeedMultiplier.Value);
		float num3 = Mathf.Clamp01(state.scanProgress + Time.deltaTime * num2 / num);
		if (PropManager.instance.UpdatePropScanProgress(ref currentLookup, num3))
		{
			currentScanData.CompleteScan(currentLookup);
			FinishCurrentTarget("Completed");
		}
	}

	private bool TryStartNextTarget()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		ScannableData scanData = default(ScannableData);
		while (scanQueue.Count > 0)
		{
			InstanceLookup val = scanQueue.Dequeue();
			queuedLookups.Remove(val);
			if (!PropManager.instance.GetScannableData(val.typeID, ref scanData) || !IsTargetEligible(val, scanData, out var _))
			{
				if (verboseLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Skipped stale scan target " + FormatLookup(val) + "."));
				}
				continue;
			}
			currentLookup = val;
			currentScanData = scanData;
			currentTargetWasScanning = false;
			hasCurrentTarget = true;
			return true;
		}
		return false;
	}

	private static bool IsTargetEligible(InstanceLookup lookup, ScannableData scanData, out PropState state)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		state = default(PropState);
		if ((Object)(object)scanData != (Object)null && !scanData.IsAlreadyScanned(lookup) && PropManager.instance.GetPropState(ref lookup, ref state))
		{
			return state.isAlive;
		}
		return false;
	}

	private void FinishCurrentTarget(string result)
	{
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)currentScanData != (Object)null && currentTargetWasScanning)
		{
			currentScanData.Scan(currentLookup, false, ref currentTargetWasScanning);
		}
		if (verboseLogging.Value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)(result + ": " + FormatLookup(currentLookup) + "."));
		}
		hasCurrentTarget = false;
		currentScanData = null;
		currentTargetWasScanning = false;
	}

	private void CancelQueue(string reason)
	{
		if (hasCurrentTarget)
		{
			FinishCurrentTarget(reason);
		}
		int count = scanQueue.Count;
		scanQueue.Clear();
		queuedLookups.Clear();
		if (count > 0)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"{reason}; discarded {count} queued target(s).");
		}
	}

	private void SetScanEffect(ScannableData scanData, InstanceLookup lookup, bool enabled)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			SetScanEffectMethod?.Invoke(scanData, new object[2] { lookup, enabled });
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not update scan highlight for " + FormatLookup(lookup) + ": " + ex.Message));
		}
	}

	private static string FormatLookup(InstanceLookup lookup)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		return $"type={lookup.typeID}, instance={lookup.instanceIndex}, chunk={lookup.chunkID}";
	}
}
[HarmonyPatch(typeof(PlayerBuilder), "Update")]
internal static class ScannerRuntimeTickPatch
{
	private static void Postfix(PlayerBuilder __instance)
	{
		JoshMassScanPlugin instance = JoshMassScanPlugin.Instance;
		instance?.ConfirmRuntimePatch("PlayerBuilder.Update");
		instance?.Tick(__instance);
	}
}
[HarmonyPatch(typeof(FactorySimManager), "SimUpdateAll")]
internal static class ScannerFactorySimulationTickPatch
{
	private static void Postfix()
	{
		JoshMassScanPlugin instance = JoshMassScanPlugin.Instance;
		PlayerBuilder builder = (((Object)(object)Player.instance != (Object)null) ? Player.instance.builder : null);
		instance?.ConfirmRuntimePatch("FactorySimManager.SimUpdateAll");
		instance?.Tick(builder);
	}
}
[HarmonyPatch(typeof(PlayerBuilder), "DeconstructionUpdate")]
internal static class ScannerBoxPreviewPatch
{
	private static readonly MethodInfo ClearOldHighlightsMethod = AccessTools.Method(typeof(PlayerBuilder), "ClearOldMaterialHighlights", (Type[])null, (Type[])null);

	private static void Postfix(PlayerBuilder __instance, List<GenericMachineInstanceRef> ___targetDeconstructables, HashSet<MachineInstanceRef<TransitCableInstance>> ___targetDeconstructableCables)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		JoshMassScanPlugin instance = JoshMassScanPlugin.Instance;
		if ((Object)(object)instance == (Object)null || !JoshMassScanPlugin.IsScannerMode(__instance))
		{
			instance?.ClearPreview();
			return;
		}
		ClearOldHighlightsMethod?.Invoke(__instance, null);
		___targetDeconstructables.Clear();
		___targetDeconstructableCables.Clear();
		if (__instance.UsingEraserBox)
		{
			instance.RefreshPreview(__instance.EraserBoxBounds);
		}
		else
		{
			instance.ClearPreview();
		}
	}
}
[HarmonyPatch(typeof(PlayerBuilder), "ProcessInput")]
internal static class ScannerBoxCommitPatch
{
	private static void Prefix(PlayerBuilder __instance, List<GenericMachineInstanceRef> ___targetDeconstructables, HashSet<MachineInstanceRef<TransitCableInstance>> ___targetDeconstructableCables)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		JoshMassScanPlugin instance = JoshMassScanPlugin.Instance;
		instance?.ObserveModeInput(__instance);
		if (!((Object)(object)instance == (Object)null) && JoshMassScanPlugin.IsScannerMode(__instance))
		{
			if (InputHandler.instance.EraseUp && __instance.UsingEraserBox)
			{
				instance.CommitSelection(__instance.EraserBoxBounds);
			}
			___targetDeconstructables.Clear();
			___targetDeconstructableCables.Clear();
		}
	}
}
[HarmonyPatch(typeof(NetworkMessageRelay), "SendNetworkAction")]
internal static class BlockEraseActionInScannerModePatch
{
	private static bool Prefix(NetworkAction action)
	{
		if (action is EraseMachineAction)
		{
			return !JoshMassScanPlugin.IsScannerMode();
		}
		return true;
	}
}