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