Decompiled source of RemainingValueTracker v0.1.1

plugins/RemainingValueTracker/RemainingValueTracker.dll

Decompiled a day ago
using System;
using System.Collections;
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 BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RemainingValueTracker")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+2ecebdac645edc5ee1df86f6ccd60ee8e75c3a72")]
[assembly: AssemblyProduct("RemainingValueTracker")]
[assembly: AssemblyTitle("RemainingValueTracker")]
[assembly: AssemblyVersion("0.1.1.0")]
[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 RemainingValueTracker
{
	[BepInPlugin("DarkSpider90.RemainingValueTracker", "Remaining Value Tracker", "0.1.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private enum TrackingMode
		{
			Value,
			Count
		}

		private readonly struct TrackerProgress
		{
			internal static readonly TrackerProgress Empty = new TrackerProgress(0f, 0f);

			internal float Discovered { get; }

			internal float Total { get; }

			internal float Percent
			{
				get
				{
					if (!(Total <= 0f))
					{
						return Discovered / Total * 100f;
					}
					return 0f;
				}
			}

			internal bool HasData => Total > 0f;

			internal TrackerProgress(float discovered, float total)
			{
				Discovered = discovered;
				Total = total;
			}
		}

		internal const string PluginGuid = "DarkSpider90.RemainingValueTracker";

		internal const string PluginName = "Remaining Value Tracker";

		internal const string PluginVersion = "0.1.1";

		private const float DefaultScanInterval = 0.5f;

		private static readonly Color MessageColor = new Color(0.8f, 1f, 0.35f);

		private static readonly Color MessageFlashColor = new Color(1f, 1f, 0.2f);

		private static readonly Color MessageShadowColor = new Color(0f, 0f, 0f, 0.75f);

		private static readonly FieldRef<ValuableObject, bool> DiscoveredRef = AccessTools.FieldRefAccess<ValuableObject, bool>("discovered");

		private static readonly FieldRef<ValuableObject, bool> DollarValueSetRef = AccessTools.FieldRefAccess<ValuableObject, bool>("dollarValueSet");

		private static readonly FieldRef<ValuableObject, float> DollarValueOriginalRef = AccessTools.FieldRefAccess<ValuableObject, float>("dollarValueOriginal");

		private ConfigEntry<bool> _enableMod;

		private ConfigEntry<TrackingMode> _trackingMode;

		private ConfigEntry<float> _triggerPercent;

		private ConfigEntry<float> _scanInterval;

		private ConfigEntry<KeyboardShortcut> _revealHotkey;

		private ConfigEntry<bool> _showMessage;

		private ConfigEntry<float> _messageDuration;

		private ConfigEntry<string> _messageText;

		private ConfigEntry<bool> _debugLogs;

		private readonly List<ValuableObject> _roundValuables = new List<ValuableObject>();

		private readonly Dictionary<int, float> _initialValues = new Dictionary<int, float>();

		private int _levelInstanceId;

		private bool _snapshotReady;

		private bool _revealed;

		private float _nextScanTime;

		private Coroutine _messageCoroutine;

		private GameObject _messageOverlay;

		private CanvasGroup _messageCanvasGroup;

		private TextMeshProUGUI _messageLabel;

		private TextMeshProUGUI _messageShadow;

		private bool _isQuitting;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_enableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable Mod", true, "Master switch for Remaining Value Tracker.");
			_trackingMode = ((BaseUnityPlugin)this).Config.Bind<TrackingMode>("General", "Tracking Mode", TrackingMode.Value, "Value reveals remaining valuables after the configured percent of total value is discovered. Count uses item count instead.");
			_triggerPercent = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Trigger Percent", 85f, new ConfigDescription("Percent of the round's valuables that must be discovered before all remaining valuables are revealed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			_scanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Scan Interval", 0.5f, new ConfigDescription("Seconds between tracker checks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>()));
			_revealHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "Reveal Hotkey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Press this key to reveal all remaining valuables immediately, as if the configured threshold was reached.");
			_showMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Show Message", true, "Show a top-center message when remaining valuables are revealed.");
			_messageDuration = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Message Duration", 4f, new ConfigDescription("Seconds to keep the reveal message visible.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 15f), Array.Empty<object>()));
			_messageText = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Message Text", "All remaining valuables discovered", "Message shown when the tracker reveals the remaining valuables.");
			_debugLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Debug Logs", false, "Print tracker snapshot and reveal details to the BepInEx log.");
			Log.LogInfo((object)"Remaining Value Tracker v0.1.1 loaded for R.E.P.O. v0.4.0.");
		}

		private void OnApplicationQuit()
		{
			_isQuitting = true;
			CleanupMessageOverlay();
		}

		private void OnDestroy()
		{
			_isQuitting = true;
			CleanupMessageOverlay();
		}

		private void Update()
		{
			if (_isQuitting || !_enableMod.Value)
			{
				return;
			}
			if (!LevelIsReady())
			{
				ResetRound();
				return;
			}
			int instanceID = ((Object)LevelGenerator.Instance).GetInstanceID();
			if (_levelInstanceId != instanceID)
			{
				ResetRound(instanceID);
			}
			if (TryRevealByHotkey() || _revealed || Time.time < _nextScanTime)
			{
				return;
			}
			_nextScanTime = Time.time + Mathf.Max(0.1f, _scanInterval.Value);
			if (!_snapshotReady && !TryBuildSnapshot())
			{
				return;
			}
			TrackerProgress trackerProgress = CalculateProgress();
			if (trackerProgress.HasData)
			{
				if (_debugLogs.Value)
				{
					Log.LogInfo((object)$"Progress: {trackerProgress.Percent:0.##}% by {_trackingMode.Value} ({trackerProgress.Discovered:0.##}/{trackerProgress.Total:0.##}).");
				}
				float num = Mathf.Clamp(_triggerPercent.Value, 1f, 100f);
				if (trackerProgress.Percent >= num)
				{
					Log.LogInfo((object)$"Reveal threshold reached: {trackerProgress.Percent:0.##}% >= {num:0.##}%.");
					RevealRemaining();
				}
			}
		}

		private bool TryRevealByHotkey()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (!_revealed)
			{
				KeyboardShortcut value = _revealHotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					if (!_snapshotReady && !TryBuildSnapshot())
					{
						Log.LogWarning((object)"Reveal hotkey was pressed, but valuables are not ready to scan yet.");
						return false;
					}
					Log.LogInfo((object)"Reveal hotkey pressed.");
					RevealRemaining();
					return true;
				}
			}
			return false;
		}

		private static bool LevelIsReady()
		{
			try
			{
				return (Object)(object)RunManager.instance != (Object)null && (Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated && (Object)(object)ValuableDirector.instance != (Object)null && (Object)(object)Map.Instance != (Object)null && SemiFunc.RunIsLevel();
			}
			catch (Exception)
			{
				return false;
			}
		}

		private bool TryBuildSnapshot()
		{
			ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>(false);
			if (array == null || array.Length == 0)
			{
				return false;
			}
			List<ValuableObject> list = array.Where((ValuableObject valuable) => (Object)(object)valuable != (Object)null && ((Behaviour)valuable).isActiveAndEnabled).Distinct().ToList();
			if (list.Count == 0 || list.Any((ValuableObject valuable) => !DollarValueSetRef.Invoke(valuable)))
			{
				return false;
			}
			_roundValuables.Clear();
			_roundValuables.AddRange(list);
			_initialValues.Clear();
			foreach (ValuableObject roundValuable in _roundValuables)
			{
				_initialValues[((Object)roundValuable).GetInstanceID()] = Mathf.Max(0f, DollarValueOriginalRef.Invoke(roundValuable));
			}
			_snapshotReady = true;
			if (_debugLogs.Value)
			{
				float num = _initialValues.Values.Sum();
				Log.LogInfo((object)$"Snapshot ready: {_roundValuables.Count} valuables, total value {num:0}.");
			}
			return true;
		}

		private TrackerProgress CalculateProgress()
		{
			if (_roundValuables.Count == 0)
			{
				return TrackerProgress.Empty;
			}
			float num = 0f;
			float num2 = 0f;
			foreach (ValuableObject roundValuable in _roundValuables)
			{
				if ((Object)(object)roundValuable == (Object)null)
				{
					continue;
				}
				float value;
				if (_trackingMode.Value == TrackingMode.Count)
				{
					num2 += 1f;
					if (DiscoveredRef.Invoke(roundValuable))
					{
						num += 1f;
					}
				}
				else if (_initialValues.TryGetValue(((Object)roundValuable).GetInstanceID(), out value))
				{
					num2 += value;
					if (DiscoveredRef.Invoke(roundValuable))
					{
						num += value;
					}
				}
			}
			if (num2 <= 0f)
			{
				return TrackerProgress.Empty;
			}
			return new TrackerProgress(num, num2);
		}

		private void RevealRemaining()
		{
			_revealed = true;
			int num = 0;
			foreach (ValuableObject roundValuable in _roundValuables)
			{
				if (!((Object)(object)roundValuable == (Object)null) && !DiscoveredRef.Invoke(roundValuable))
				{
					try
					{
						roundValuable.Discover((State)0);
						num++;
					}
					catch (Exception ex)
					{
						Log.LogWarning((object)("Could not reveal " + ((Object)roundValuable).name + ": " + ex.Message));
					}
				}
			}
			if (_showMessage.Value)
			{
				ShowRevealMessage();
			}
			Log.LogInfo((object)$"Revealed {num} remaining valuables.");
		}

		private void ShowRevealMessage()
		{
			if (!_isQuitting)
			{
				if (_messageCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_messageCoroutine);
				}
				_messageCoroutine = ((MonoBehaviour)this).StartCoroutine(ShowRevealMessageRoutine());
			}
		}

		private IEnumerator ShowRevealMessageRoutine()
		{
			EnsureMessageOverlay();
			string value = _messageText.Value;
			float duration = Mathf.Clamp(_messageDuration.Value, 0.5f, 15f);
			float endTime = Time.time + duration;
			float fadeIn = Mathf.Min(0.2f, duration * 0.25f);
			float fadeOut = Mathf.Min(0.35f, duration * 0.3f);
			SetOverlayText(value);
			_messageOverlay.SetActive(true);
			Log.LogInfo((object)$"Showing reveal message for {duration:0.##}s.");
			while (Time.time < endTime)
			{
				float num = duration - (endTime - Time.time);
				float num2 = endTime - Time.time;
				float alpha = 1f;
				if (fadeIn > 0f && num < fadeIn)
				{
					alpha = Mathf.Clamp01(num / fadeIn);
				}
				else if (fadeOut > 0f && num2 < fadeOut)
				{
					alpha = Mathf.Clamp01(num2 / fadeOut);
				}
				_messageCanvasGroup.alpha = alpha;
				yield return null;
			}
			_messageCanvasGroup.alpha = 0f;
			_messageOverlay.SetActive(false);
			_messageCoroutine = null;
		}

		private void EnsureMessageOverlay()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_0063: 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)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_messageOverlay != (Object)null))
			{
				_messageOverlay = new GameObject("RemainingValueTracker Message Overlay");
				Object.DontDestroyOnLoad((Object)(object)_messageOverlay);
				Canvas obj = _messageOverlay.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 32760;
				CanvasScaler obj2 = _messageOverlay.AddComponent<CanvasScaler>();
				obj2.uiScaleMode = (ScaleMode)1;
				obj2.referenceResolution = new Vector2(1920f, 1080f);
				obj2.matchWidthOrHeight = 0.5f;
				_messageCanvasGroup = _messageOverlay.AddComponent<CanvasGroup>();
				_messageCanvasGroup.blocksRaycasts = false;
				_messageCanvasGroup.interactable = false;
				_messageShadow = CreateOverlayText("Message Shadow", MessageShadowColor, new Vector2(2f, -2f));
				_messageLabel = CreateOverlayText("Message", MessageColor, Vector2.zero);
				_messageOverlay.SetActive(false);
			}
		}

		private TextMeshProUGUI CreateOverlayText(string name, Color color, Vector2 offset)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(_messageOverlay.transform, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)514;
			((TMP_Text)val2).fontStyle = (FontStyles)1;
			((Graphic)val2).color = color;
			((TMP_Text)val2).fontSize = 36f;
			((TMP_Text)val2).enableAutoSizing = true;
			((TMP_Text)val2).fontSizeMin = 18f;
			((TMP_Text)val2).fontSizeMax = 38f;
			((TMP_Text)val2).enableWordWrapping = true;
			TMP_FontAsset val3 = FindExistingFont();
			if ((Object)(object)val3 != (Object)null)
			{
				((TMP_Text)val2).font = val3;
			}
			RectTransform rectTransform = ((TMP_Text)val2).rectTransform;
			rectTransform.anchorMin = new Vector2(0.5f, 1f);
			rectTransform.anchorMax = new Vector2(0.5f, 1f);
			rectTransform.pivot = new Vector2(0.5f, 1f);
			rectTransform.anchoredPosition = new Vector2(offset.x, -95f + offset.y);
			rectTransform.sizeDelta = new Vector2(1200f, 96f);
			return val2;
		}

		private static TMP_FontAsset FindExistingFont()
		{
			TextMeshProUGUI val = Object.FindObjectOfType<TextMeshProUGUI>();
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return ((TMP_Text)val).font;
		}

		private void SetOverlayText(string message)
		{
			((TMP_Text)_messageLabel).text = message;
			((TMP_Text)_messageShadow).text = message;
		}

		private void ResetRound(int levelInstanceId = 0)
		{
			StopMessageCoroutine();
			_levelInstanceId = levelInstanceId;
			_snapshotReady = false;
			_revealed = false;
			_nextScanTime = 0f;
			_roundValuables.Clear();
			_initialValues.Clear();
		}

		private void StopMessageCoroutine()
		{
			if (_messageCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_messageCoroutine);
				_messageCoroutine = null;
			}
		}

		private void CleanupMessageOverlay()
		{
			StopMessageCoroutine();
			if (!((Object)(object)_messageOverlay == (Object)null))
			{
				Object.Destroy((Object)(object)_messageOverlay);
				_messageOverlay = null;
				_messageCanvasGroup = null;
				_messageLabel = null;
				_messageShadow = null;
			}
		}
	}
}