Decompiled source of CreatureStarAlert v0.2.0

plugins/CreatureStarAlert.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CreatureStarAlert.Config;
using CreatureStarAlert.Detection;
using CreatureStarAlert.UI;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("CreatureStarAlert")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CreatureStarAlert")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("0.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
namespace CreatureStarAlert
{
	[BepInPlugin("orax.CreatureStarAlert", "CreatureStarAlert", "0.1.0")]
	internal sealed class CreatureStarAlert : BaseUnityPlugin
	{
		public const string PluginGUID = "orax.CreatureStarAlert";

		public const string PluginName = "CreatureStarAlert";

		public const string PluginVersion = "0.1.0";

		private PluginConfig _config;

		private Harmony _harmony;

		private LoadedCreatureScanner _scanner;

		private BannerUI _banner;

		private bool _wasInWorld;

		private bool _enabled;

		private volatile bool _detectionChanged;

		internal static CreatureStarAlert Instance { get; private set; }

		internal IAlertManager AlertManager { get; private set; }

		private void Awake()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected O, but got Unknown
			Instance = this;
			_config = new PluginConfig(((BaseUnityPlugin)this).Config);
			_config.DetectionChanged += OnDetectionChanged;
			_enabled = _config.Enabled;
			ICreatureFilter filter = new WildcardCreatureFilter(_config);
			_banner = new BannerUI(_config, ((BaseUnityPlugin)this).Logger);
			AlertManager = new AlertManager(_config, filter, _banner, ((BaseUnityPlugin)this).Logger);
			_scanner = new LoadedCreatureScanner(_config, AlertManager);
			_harmony = new Harmony("orax.CreatureStarAlert");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("{0} {1} loaded: stars >= {2}, ", "CreatureStarAlert", "0.1.0", _config.StarsMinimum) + "creatures '" + _config.CreaturePatterns + "', radius " + ((_config.MaxDistance > 0f) ? $"{_config.MaxDistance} m" : "no filter") + ", priority " + (_config.PreferMostStars ? "most stars" : "closest")));
		}

		private void Update()
		{
			bool flag = (Object)(object)Player.m_localPlayer != (Object)null;
			if (_wasInWorld && !flag)
			{
				AlertManager.Reset();
				_scanner.Reset();
				_banner.Clear();
			}
			_wasInWorld = flag;
			ApplyEnabled();
			ApplyDetectionChanges();
			if (flag && _enabled)
			{
				_scanner.Tick(Time.deltaTime);
				_banner.Tick(Time.deltaTime);
			}
		}

		private void ApplyEnabled()
		{
			bool enabled = _config.Enabled;
			if (enabled != _enabled)
			{
				_enabled = enabled;
				if (!enabled)
				{
					DropAlerts();
				}
			}
		}

		private void ApplyDetectionChanges()
		{
			if (_detectionChanged)
			{
				_detectionChanged = false;
				DropAlerts();
			}
		}

		private void DropAlerts()
		{
			AlertManager.Reset();
			_banner.Clear();
		}

		private void OnDetectionChanged(object sender, EventArgs e)
		{
			_detectionChanged = true;
		}

		private void OnGUI()
		{
			_banner?.OnGui();
		}

		private void OnDestroy()
		{
			if (_config != null)
			{
				_config.DetectionChanged -= OnDetectionChanged;
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
}
namespace CreatureStarAlert.UI
{
	public sealed class BannerUI : IAlertSink
	{
		private const float FadeIn = 0.25f;

		private const float FadeOut = 0.5f;

		private const float Padding = 14f;

		private const float ScreenHeightRatio = 0.035f;

		private const float MaxLead = 5f;

		private const float MinLead = 0.5f;

		private const float LeadRatio = 0.25f;

		private readonly IBannerConfig _config;

		private readonly ManualLogSource _logger;

		private readonly List<CreatureAlert> _pending = new List<CreatureAlert>();

		private CreatureAlert _current;

		private float _elapsed;

		private GUIStyle _labelStyle;

		private int _labelFontSize = -1;

		public BannerUI(IBannerConfig config, ManualLogSource logger)
		{
			_config = config ?? throw new ArgumentNullException("config");
			_logger = logger ?? throw new ArgumentNullException("logger");
		}

		public void Enqueue(CreatureAlert alert)
		{
			if (alert == null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			ForgetUnavailable();
			if (_pending.Count >= _config.MaxQueuedAlerts)
			{
				int num = FindWorst(localPlayer);
				if (num < 0 || !Ranks(alert, _pending[num], localPlayer))
				{
					_logger.LogDebug((object)("[StarAlert] Banner queue is full, dropping " + alert.DisplayName));
					return;
				}
				_pending.RemoveAt(num);
			}
			_pending.Add(alert);
		}

		public void Clear()
		{
			_pending.Clear();
			_current = null;
			_elapsed = 0f;
		}

		public void Tick(float deltaTime)
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				Clear();
				return;
			}
			if (_current != null && !_current.IsAvailable)
			{
				_current = null;
				_elapsed = 0f;
			}
			float duration = GetDuration();
			if (_current != null)
			{
				_elapsed += deltaTime;
				if (_elapsed >= duration)
				{
					_current = null;
					_elapsed = 0f;
				}
			}
			ShowBest();
		}

		private void ShowBest()
		{
			Player localPlayer = Player.m_localPlayer;
			ForgetUnavailable();
			int num = FindBest(localPlayer);
			if (_current == null)
			{
				Promote(num);
			}
			else if (num >= 0 && Beats(_pending[num], _current, localPlayer))
			{
				CreatureAlert current = _current;
				if (Promote(num))
				{
					_pending.Add(current);
				}
			}
		}

		private bool Promote(int index)
		{
			if (index < 0 || index >= _pending.Count)
			{
				return false;
			}
			_current = _pending[index];
			_pending.RemoveAt(index);
			return true;
		}

		private int FindBest(Player player)
		{
			int num = -1;
			for (int i = 0; i < _pending.Count; i++)
			{
				if (num < 0 || Ranks(_pending[i], _pending[num], player))
				{
					num = i;
				}
			}
			return num;
		}

		private int FindWorst(Player player)
		{
			int num = -1;
			for (int i = 0; i < _pending.Count; i++)
			{
				if (num < 0 || Ranks(_pending[num], _pending[i], player))
				{
					num = i;
				}
			}
			return num;
		}

		private bool Ranks(CreatureAlert left, CreatureAlert right, Player player)
		{
			return AlertPriority.Compare(left.Stars, DistanceOf(left, player), right.Stars, DistanceOf(right, player), _config.PreferMostStars) < 0;
		}

		private bool Beats(CreatureAlert candidate, CreatureAlert current, Player player)
		{
			if (_config.PreferMostStars && candidate.Stars != current.Stars)
			{
				return candidate.Stars > current.Stars;
			}
			float num = DistanceOf(candidate, player);
			float num2 = DistanceOf(current, player);
			return num + RequiredLead(num2) < num2;
		}

		private static float RequiredLead(float distance)
		{
			return Mathf.Clamp(distance * 0.25f, 0.5f, 5f);
		}

		private void ForgetUnavailable()
		{
			for (int num = _pending.Count - 1; num >= 0; num--)
			{
				if (!_pending[num].IsAvailable)
				{
					_pending.RemoveAt(num);
				}
			}
		}

		private static float DistanceOf(CreatureAlert alert, Player player)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if (!alert.IsAvailable)
			{
				return float.MaxValue;
			}
			if ((Object)(object)player == (Object)null)
			{
				return alert.Distance;
			}
			return Vector3.Distance(((Component)player).transform.position, ((Component)alert.Character).transform.position);
		}

		public void OnGui()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: 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)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: 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_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (_current == null || (Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			float duration = GetDuration();
			float num = ComputeAlpha(duration);
			if (!(num <= 0f))
			{
				string text = BuildText(localPlayer, _current);
				int fontSize = GetFontSize();
				GUIStyle labelStyle = GetLabelStyle(fontSize);
				Vector2 val = labelStyle.CalcSize(new GUIContent(text));
				float num2 = val.x + 28f;
				float num3 = val.y + 28f;
				Vector2 origin = GetOrigin(num2, num3);
				Color color = GUI.color;
				if (_config.ShowRectangle)
				{
					GUI.color = new Color(0f, 0f, 0f, (float)_config.RectangleOpacity / 100f * num);
					GUI.Box(new Rect(origin.x, origin.y, num2, num3), GUIContent.none);
				}
				GUI.color = new Color(1f, 1f, 1f, num);
				GUI.Label(new Rect(origin.x, origin.y, num2, num3), text, labelStyle);
				GUI.color = color;
			}
		}

		private string BuildText(Player player, CreatureAlert alert)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: 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_00da: Unknown result type (might be due to invalid IL or missing references)
			string text = (string.IsNullOrEmpty(alert.DisplayName) ? alert.PrefabName : alert.DisplayName);
			int count = Mathf.Clamp(alert.Stars, 1, 9);
			string text2 = "<color=" + ToHex(_config.NameColor) + ">" + text + "</color>  <color=" + ToHex(_config.StarColor) + ">" + new string('★', count) + "</color>";
			if (_config.ShowDistance && alert.IsAvailable)
			{
				float num = Vector3.Distance(((Component)player).transform.position, ((Component)alert.Character).transform.position);
				text2 += $"  <color={ToHex(_config.DistanceColor)}>({num:0} m)</color>";
			}
			return text2;
		}

		private static string ToHex(Color color)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			return "#" + ColorUtility.ToHtmlStringRGBA(color);
		}

		private int GetFontSize()
		{
			if (_config.FontSize > 0)
			{
				return _config.FontSize;
			}
			return Mathf.Max(14, Mathf.RoundToInt((float)Screen.height * 0.035f));
		}

		private float GetDuration()
		{
			if (_config.DurationSeconds <= 0f)
			{
				return float.MaxValue;
			}
			return _config.DurationSeconds;
		}

		private float ComputeAlpha(float duration)
		{
			float num = Mathf.Min(0.25f, duration * 0.25f);
			float num2 = Mathf.Min(0.5f, duration * 0.25f);
			return Mathf.Clamp01(Mathf.Min(1f, _elapsed / num) * Mathf.Min(1f, (duration - _elapsed) / num2));
		}

		private Vector2 GetOrigin(float width, float height)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			float num = ToScreenCoordinate(_config.PositionX, Screen.width);
			float num2 = ToScreenCoordinate(_config.PositionY, Screen.height);
			return new Vector2(Mathf.Round(num - AnchorOffsetX(_config.Anchor, width)), Mathf.Round(num2 - AnchorOffsetY(_config.Anchor, height)));
		}

		private float ToScreenCoordinate(int position, float axisLength)
		{
			if (_config.PositionUnit == PositionUnit.Pixels)
			{
				return position;
			}
			return axisLength * ((float)position / 100f);
		}

		private static float AnchorOffsetX(BannerAnchor anchor, float width)
		{
			switch (anchor)
			{
			case BannerAnchor.TopLeft:
			case BannerAnchor.Left:
			case BannerAnchor.BottomLeft:
				return 0f;
			case BannerAnchor.TopRight:
			case BannerAnchor.Right:
			case BannerAnchor.BottomRight:
				return width;
			default:
				return width * 0.5f;
			}
		}

		private static float AnchorOffsetY(BannerAnchor anchor, float height)
		{
			switch (anchor)
			{
			case BannerAnchor.TopLeft:
			case BannerAnchor.TopCenter:
			case BannerAnchor.TopRight:
				return 0f;
			case BannerAnchor.BottomLeft:
			case BannerAnchor.BottomCenter:
			case BannerAnchor.BottomRight:
				return height;
			default:
				return height * 0.5f;
			}
		}

		private GUIStyle GetLabelStyle(int fontSize)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: 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_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_0067: Expected O, but got Unknown
			if (_labelStyle != null && _labelFontSize == fontSize)
			{
				return _labelStyle;
			}
			_labelStyle = new GUIStyle(GUI.skin.label)
			{
				richText = true,
				alignment = (TextAnchor)4,
				fontStyle = (FontStyle)1,
				fontSize = fontSize,
				padding = new RectOffset(0, 0, 0, 0),
				margin = new RectOffset(0, 0, 0, 0)
			};
			_labelFontSize = fontSize;
			return _labelStyle;
		}
	}
}
namespace CreatureStarAlert.Detection
{
	public sealed class AlertManager : IAlertManager
	{
		private const string CloneSuffix = "(Clone)";

		private readonly IDetectionConfig _config;

		private readonly ICreatureFilter _filter;

		private readonly IAlertSink _sink;

		private readonly ManualLogSource _logger;

		private readonly HashSet<ZDOID> _alerted = new HashSet<ZDOID>();

		public AlertManager(IDetectionConfig config, ICreatureFilter filter, IAlertSink sink, ManualLogSource logger)
		{
			_config = config ?? throw new ArgumentNullException("config");
			_filter = filter ?? throw new ArgumentNullException("filter");
			_sink = sink ?? throw new ArgumentNullException("sink");
			_logger = logger ?? throw new ArgumentNullException("logger");
		}

		public AlertOutcome Report(Character character, AlertReason reason)
		{
			CreatureAlert alert;
			AlertOutcome alertOutcome = Evaluate(character, reason, out alert);
			if (_config.DebugLogging && (Object)(object)character != (Object)null && character.GetLevel() - 1 >= _config.StarsMinimum && alertOutcome != AlertOutcome.AlreadyAlerted)
			{
				_logger.LogDebug((object)$"[StarAlert] {reason} '{((Object)character).name}': {alertOutcome}");
			}
			if (alertOutcome == AlertOutcome.Alerted)
			{
				_sink.Enqueue(alert);
				_logger.LogInfo((object)($"[StarAlert] {reason}: {alert.DisplayName} ({alert.PrefabName}) " + $"with {alert.Stars} star(s) at {alert.Distance:0} m"));
			}
			return alertOutcome;
		}

		public void Reset()
		{
			_alerted.Clear();
		}

		public void ForgetUnavailable(IEnumerable<ZDOID> relevantCreatures)
		{
			if (relevantCreatures != null)
			{
				_alerted.IntersectWith(relevantCreatures);
			}
		}

		private AlertOutcome Evaluate(Character character, AlertReason reason, out CreatureAlert alert)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_008e: 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_00f2: Unknown result type (might be due to invalid IL or missing references)
			alert = null;
			if (!_config.Enabled)
			{
				return AlertOutcome.Disabled;
			}
			if ((Object)(object)character == (Object)null || character.IsPlayer())
			{
				return AlertOutcome.NotWatched;
			}
			int num = character.GetLevel() - 1;
			if (num < _config.StarsMinimum)
			{
				return AlertOutcome.NotWatched;
			}
			if (_config.IgnoreTamed && character.IsTamed())
			{
				return AlertOutcome.NotWatched;
			}
			ZDOID zDOID = character.GetZDOID();
			if (((ZDOID)(ref zDOID)).IsNone())
			{
				return AlertOutcome.NotWatched;
			}
			if (_alerted.Contains(zDOID))
			{
				return AlertOutcome.AlreadyAlerted;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return AlertOutcome.OutOfRange;
			}
			float num2 = Vector3.Distance(((Component)localPlayer).transform.position, ((Component)character).transform.position);
			if (_config.MaxDistance > 0f && num2 > _config.MaxDistance)
			{
				return AlertOutcome.OutOfRange;
			}
			string prefabName = GetPrefabName(character);
			string hoverName = character.GetHoverName();
			if (!IsWatched(prefabName) && !IsWatched(hoverName))
			{
				return AlertOutcome.NotWatched;
			}
			_alerted.Add(zDOID);
			alert = new CreatureAlert(prefabName, hoverName, num, reason, character, num2, _config.MaxDistance);
			return AlertOutcome.Alerted;
		}

		private bool IsWatched(string creatureName)
		{
			if (!string.IsNullOrEmpty(creatureName))
			{
				return _filter.IsWatched(creatureName);
			}
			return false;
		}

		private static string GetPrefabName(Character character)
		{
			string text = ((Object)character).name;
			if (text.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - "(Clone)".Length);
			}
			return text.TrimEnd(Array.Empty<char>());
		}
	}
	public static class AlertPriority
	{
		public static int Compare(int leftStars, float leftDistance, int rightStars, float rightDistance, bool preferMostStars)
		{
			if (preferMostStars && leftStars != rightStars)
			{
				return rightStars.CompareTo(leftStars);
			}
			return leftDistance.CompareTo(rightDistance);
		}
	}
	public enum AlertReason
	{
		Spawn,
		Loaded
	}
	public sealed class CreatureAlert
	{
		public string PrefabName { get; }

		public string DisplayName { get; }

		public int Stars { get; }

		public AlertReason Reason { get; }

		public Character Character { get; }

		public float MaxDistance { get; }

		public bool IsAvailable
		{
			get
			{
				//IL_0036: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)Character == (Object)null)
				{
					return false;
				}
				if (MaxDistance <= 0f || (Object)(object)Player.m_localPlayer == (Object)null)
				{
					return true;
				}
				return Vector3.Distance(((Component)Player.m_localPlayer).transform.position, ((Component)Character).transform.position) <= MaxDistance;
			}
		}

		public Vector3 Position { get; }

		public float Distance { get; }

		public CreatureAlert(string prefabName, string displayName, int stars, AlertReason reason, Character character, float distance, float maxDistance)
		{
			//IL_0033: 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)
			PrefabName = prefabName;
			DisplayName = displayName;
			Stars = stars;
			Reason = reason;
			Character = character;
			Position = ((Component)character).transform.position;
			Distance = distance;
			MaxDistance = maxDistance;
		}
	}
	[HarmonyPatch]
	internal static class CharacterSetLevelPatch
	{
		[HarmonyPatch(typeof(Character), "SetLevel")]
		private static void Postfix(Character __instance)
		{
			CreatureStarAlert.Instance?.AlertManager?.Report(__instance, AlertReason.Spawn);
		}
	}
	public enum AlertOutcome
	{
		Disabled,
		NotWatched,
		OutOfRange,
		AlreadyAlerted,
		Alerted
	}
	public interface IAlertManager
	{
		AlertOutcome Report(Character character, AlertReason reason);

		void Reset();

		void ForgetUnavailable(IEnumerable<ZDOID> relevantCreatures);
	}
	public interface IAlertSink
	{
		void Enqueue(CreatureAlert alert);
	}
	public interface ICreatureFilter
	{
		bool IsWatched(string creatureName);
	}
	public sealed class LoadedCreatureScanner
	{
		private readonly IDetectionConfig _config;

		private readonly IAlertManager _manager;

		private readonly HashSet<ZDOID> _keptIds = new HashSet<ZDOID>();

		private readonly List<Character> _ranked = new List<Character>();

		private readonly Comparison<Character> _bestFirst;

		private float _accumulator;

		public LoadedCreatureScanner(IDetectionConfig config, IAlertManager manager)
		{
			_config = config ?? throw new ArgumentNullException("config");
			_manager = manager ?? throw new ArgumentNullException("manager");
			_bestFirst = (Character left, Character right) => AlertPriority.Compare(left.GetLevel() - 1, DistanceToPlayer(left), right.GetLevel() - 1, DistanceToPlayer(right), _config.PreferMostStars);
		}

		public void Reset()
		{
			_keptIds.Clear();
			_ranked.Clear();
			_accumulator = 0f;
		}

		public void Tick(float deltaTime)
		{
			_accumulator += deltaTime;
			if (!(_accumulator < _config.ScanIntervalSeconds))
			{
				_accumulator = 0f;
				Scan();
			}
		}

		private void Scan()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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)
			_keptIds.Clear();
			_ranked.Clear();
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if ((Object)(object)allCharacter == (Object)null)
				{
					continue;
				}
				ZDOID zDOID = allCharacter.GetZDOID();
				if (!((ZDOID)(ref zDOID)).IsNone())
				{
					if (KeepsAlert(allCharacter))
					{
						_keptIds.Add(zDOID);
					}
					if (_config.IncludeLoadedCreatures)
					{
						_ranked.Add(allCharacter);
					}
				}
			}
			_ranked.Sort(_bestFirst);
			foreach (Character item in _ranked)
			{
				_manager.Report(item, AlertReason.Loaded);
			}
			if (_config.ReAlertOnZoneLoad)
			{
				_manager.ForgetUnavailable(_keptIds);
			}
		}

		private bool KeepsAlert(Character character)
		{
			if (!(_config.MaxDistance <= 0f))
			{
				return DistanceToPlayer(character) <= _config.MaxDistance;
			}
			return true;
		}

		private static float DistanceToPlayer(Character character)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return float.MaxValue;
			}
			return Vector3.Distance(((Component)localPlayer).transform.position, ((Component)character).transform.position);
		}
	}
	public sealed class WildcardCreatureFilter : ICreatureFilter
	{
		private readonly ICreatureFilterConfig _config;

		private string _compiledPatterns;

		private List<Regex> _patterns = new List<Regex>();

		public WildcardCreatureFilter(ICreatureFilterConfig config)
		{
			_config = config ?? throw new ArgumentNullException("config");
		}

		public bool IsWatched(string creatureName)
		{
			if (string.IsNullOrEmpty(creatureName))
			{
				return false;
			}
			List<Regex> patterns = GetPatterns();
			for (int i = 0; i < patterns.Count; i++)
			{
				if (patterns[i].IsMatch(creatureName))
				{
					return true;
				}
			}
			return false;
		}

		private List<Regex> GetPatterns()
		{
			string creaturePatterns = _config.CreaturePatterns;
			if (string.Equals(creaturePatterns, _compiledPatterns, StringComparison.Ordinal))
			{
				return _patterns;
			}
			_compiledPatterns = creaturePatterns;
			_patterns = Compile(creaturePatterns);
			return _patterns;
		}

		private static List<Regex> Compile(string raw)
		{
			List<Regex> list = new List<Regex>();
			if (string.IsNullOrWhiteSpace(raw))
			{
				return list;
			}
			string[] array = raw.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					string pattern = "^" + Regex.Escape(text).Replace("\\*", ".*") + "$";
					list.Add(new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant));
				}
			}
			return list;
		}
	}
}
namespace CreatureStarAlert.Config
{
	public enum BannerAnchor
	{
		TopLeft,
		TopCenter,
		TopRight,
		Left,
		Center,
		Right,
		BottomLeft,
		BottomCenter,
		BottomRight
	}
	public interface IAlertPriorityConfig
	{
		bool PreferMostStars { get; }
	}
	public interface IDetectionConfig : IAlertPriorityConfig
	{
		bool Enabled { get; }

		bool DebugLogging { get; }

		int StarsMinimum { get; }

		float MaxDistance { get; }

		bool IgnoreTamed { get; }

		bool IncludeLoadedCreatures { get; }

		bool ReAlertOnZoneLoad { get; }

		float ScanIntervalSeconds { get; }
	}
	public interface ICreatureFilterConfig
	{
		string CreaturePatterns { get; }
	}
	public interface IBannerConfig : IAlertPriorityConfig
	{
		float DurationSeconds { get; }

		BannerAnchor Anchor { get; }

		int PositionX { get; }

		int PositionY { get; }

		PositionUnit PositionUnit { get; }

		bool ShowDistance { get; }

		Color NameColor { get; }

		Color StarColor { get; }

		Color DistanceColor { get; }

		bool ShowRectangle { get; }

		int RectangleOpacity { get; }

		int MaxQueuedAlerts { get; }

		int FontSize { get; }
	}
	public sealed class PluginConfig : IDetectionConfig, IAlertPriorityConfig, ICreatureFilterConfig, IBannerConfig
	{
		private const string GeneralSection = "General";

		private const string DetectionSection = "Detection";

		private const string AlertSection = "Alert";

		private const string PositionTopLeft = "TopLeft";

		private const string PositionTopCenter = "TopCenter";

		private const string PositionTopRight = "TopRight";

		private const string PositionLeft = "Left";

		private const string PositionCenter = "Center";

		private const string PositionRight = "Right";

		private const string PositionBottomLeft = "BottomLeft";

		private const string PositionBottomCenter = "BottomCenter";

		private const string PositionBottomRight = "BottomRight";

		private const string PositionUnitPercent = "Percent";

		private const string PositionUnitPixels = "Pixels";

		private const int MaxScreenCoordinate = 10000;

		private const float MaxLoadedCreatureDistance = 500f;

		private readonly ConfigEntry<bool> _enabled;

		private readonly ConfigEntry<bool> _debugLogging;

		private readonly ConfigEntry<int> _starsMinimum;

		private readonly ConfigEntry<string> _creaturePatterns;

		private readonly ConfigEntry<float> _maxDistance;

		private readonly ConfigEntry<bool> _ignoreTamed;

		private readonly ConfigEntry<bool> _includeLoadedCreatures;

		private readonly ConfigEntry<bool> _reAlertOnZoneLoad;

		private readonly ConfigEntry<float> _scanIntervalSeconds;

		private readonly ConfigEntry<float> _durationSeconds;

		private readonly ConfigEntry<string> _anchor;

		private readonly ConfigEntry<int> _positionX;

		private readonly ConfigEntry<int> _positionY;

		private readonly ConfigEntry<string> _positionUnit;

		private readonly ConfigEntry<Color> _nameColor;

		private readonly ConfigEntry<Color> _starColor;

		private readonly ConfigEntry<Color> _distanceColor;

		private readonly ConfigEntry<bool> _showRectangle;

		private readonly ConfigEntry<int> _rectangleOpacity;

		private readonly ConfigEntry<bool> _showDistance;

		private readonly ConfigEntry<int> _maxQueuedAlerts;

		private readonly ConfigEntry<int> _fontSize;

		private readonly ConfigEntry<bool> _preferMostStars;

		public bool Enabled => _enabled.Value;

		public bool DebugLogging => _debugLogging.Value;

		public int StarsMinimum => _starsMinimum.Value;

		public float MaxDistance => _maxDistance.Value;

		public bool IgnoreTamed => _ignoreTamed.Value;

		public bool IncludeLoadedCreatures => _includeLoadedCreatures.Value;

		public bool ReAlertOnZoneLoad => _reAlertOnZoneLoad.Value;

		public float ScanIntervalSeconds => _scanIntervalSeconds.Value;

		public string CreaturePatterns => _creaturePatterns.Value;

		public float DurationSeconds => _durationSeconds.Value;

		public BannerAnchor Anchor => ParseAnchor(_anchor.Value);

		public int PositionX => _positionX.Value;

		public int PositionY => _positionY.Value;

		public PositionUnit PositionUnit => ParseUnit(_positionUnit.Value);

		public bool ShowDistance => _showDistance.Value;

		public Color NameColor => _nameColor.Value;

		public Color StarColor => _starColor.Value;

		public Color DistanceColor => _distanceColor.Value;

		public bool ShowRectangle => _showRectangle.Value;

		public int RectangleOpacity => _rectangleOpacity.Value;

		public int MaxQueuedAlerts => _maxQueuedAlerts.Value;

		public int FontSize => _fontSize.Value;

		public bool PreferMostStars => _preferMostStars.Value;

		public event EventHandler DetectionChanged;

		public PluginConfig(ConfigFile file)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Expected O, but got Unknown
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Expected O, but got Unknown
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Expected O, but got Unknown
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Expected O, but got Unknown
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Expected O, but got Unknown
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Expected O, but got Unknown
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_0345: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_039f: Expected O, but got Unknown
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e9: Expected O, but got Unknown
			//IL_040d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0417: Expected O, but got Unknown
			_enabled = Bind(file, "General", "Enabled", defaultValue: true, "Enable the creature star alerts.");
			_debugLogging = Bind(file, "General", "DebugLogging", defaultValue: false, "Log every detection decision. Verbose, intended for troubleshooting only.");
			_starsMinimum = Bind(file, "Detection", "StarsMinimum", 2, new ConfigDescription("Minimum number of stars required to raise an alert (2 = 2 stars and above).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 9), Array.Empty<object>()));
			_creaturePatterns = Bind(file, "Detection", "CreaturePatterns", "*", "Comma separated list of creatures to watch. Accepts the prefab name (for example 'Moose') or the localised display name. Supports the '*' wildcard and is case insensitive. '*' watches every creature.");
			_maxDistance = Bind(file, "Detection", "MaxDistance", 100f, new ConfigDescription("Maximum distance in metres between the player and the creature for an alert. 0 = no distance filter: every creature the game has loaded is considered. The game unloads zones and their creatures beyond its simulation distance (about 200 m with the default settings), so the setting stops at 500 m: a larger value could never make a difference.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 500f), Array.Empty<object>()));
			_ignoreTamed = Bind(file, "Detection", "IgnoreTamed", defaultValue: true, "Ignore tamed creatures: they never raise an alert. Their tamed offspring inherit their stars, so a farm would otherwise keep the banner busy.");
			_includeLoadedCreatures = Bind(file, "Detection", "IncludeLoadedCreatures", defaultValue: true, "Also alert for star creatures found when entering or loading a zone, not only on fresh spawns.");
			_reAlertOnZoneLoad = Bind(file, "Detection", "ReAlertOnZoneLoad", defaultValue: true, "Forget the alert of a creature when it unloads or leaves the detection radius, so appearing again raises a new alert. When false, each creature is only alerted once per world session.");
			_scanIntervalSeconds = Bind(file, "Detection", "ScanIntervalSeconds", 3f, new ConfigDescription("How often, in seconds, loaded zones are scanned for star creatures.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 30f), Array.Empty<object>()));
			_durationSeconds = Bind(file, "Alert", "DurationSeconds", 0f, new ConfigDescription("How long the banner stays on screen, in seconds. 0 = keep it on screen until the creature is unloaded or dies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), Array.Empty<object>()));
			_anchor = Bind(file, "Alert", "Anchor", "Center", new ConfigDescription("Point of the banner placed at the position: TopLeft puts its top left corner there, Center its centre, and so on. Changing it barely moves the banner (by its own size at most): use Position x and Position y to move the banner on screen.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[9] { "TopLeft", "TopCenter", "TopRight", "Left", "Center", "Right", "BottomLeft", "BottomCenter", "BottomRight" }), Array.Empty<object>()));
			_positionX = Bind(file, "Alert", "PositionX", 50, new ConfigDescription("Horizontal position of the anchor: percent of the screen width (0 = left edge, 100 = right edge), or pixels from the left edge when the unit is Pixels.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>()));
			_positionY = Bind(file, "Alert", "PositionY", 5, new ConfigDescription("Vertical position of the anchor: percent of the screen height (0 = top edge, 100 = bottom edge), or pixels from the top edge when the unit is Pixels.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>()));
			_positionUnit = Bind(file, "Alert", "PositionUnit", "Percent", new ConfigDescription("Whether Position x and Position y are percents of the screen or pixels.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Percent", "Pixels" }), Array.Empty<object>()));
			_nameColor = Bind<Color>(file, "Alert", "NameColor", Color32.op_Implicit(new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue)), "Colour of the creature name, as a RRGGBBAA hex value (FFFFFFFF = opaque white).");
			_starColor = Bind<Color>(file, "Alert", "StarColor", Color32.op_Implicit(new Color32(byte.MaxValue, (byte)197, (byte)59, byte.MaxValue)), "Colour of the stars, as a RRGGBBAA hex value (FFC53BFF = the default gold).");
			_distanceColor = Bind<Color>(file, "Alert", "DistanceColor", Color32.op_Implicit(new Color32((byte)168, (byte)168, (byte)168, byte.MaxValue)), "Colour of the distance, as a RRGGBBAA hex value (A8A8A8FF = light grey).");
			_showRectangle = Bind(file, "Alert", "ShowRectangle", defaultValue: true, "Draw the rectangle behind the banner text. When off, only the text is shown.");
			_rectangleOpacity = Bind(file, "Alert", "RectangleOpacity", 65, new ConfigDescription("Opacity of the rectangle behind the banner text, in percent: 0 = invisible, 100 = opaque black. Applied on top of the fade.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			_showDistance = Bind(file, "Alert", "ShowDistance", defaultValue: true, "Show the live distance to the creature in the banner.");
			_maxQueuedAlerts = Bind(file, "Alert", "MaxQueuedAlerts", 3, new ConfigDescription("Maximum number of alerts waiting to be displayed. The best ones on the criterion are kept when it is full.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			_fontSize = Bind(file, "Alert", "FontSize", 0, new ConfigDescription("Font size of the banner in pixels. 0 = automatic (scaled to the screen height).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 120), Array.Empty<object>()));
			_preferMostStars = Bind(file, "Alert", "PreferMostStars", defaultValue: false, "When several star creatures are detected, show the one with the most stars instead of the closest one. Ties are always broken by distance.");
			_starsMinimum.SettingChanged += OnDetectionChanged;
			_creaturePatterns.SettingChanged += OnDetectionChanged;
			_maxDistance.SettingChanged += OnDetectionChanged;
			_ignoreTamed.SettingChanged += OnDetectionChanged;
			_includeLoadedCreatures.SettingChanged += OnDetectionChanged;
		}

		private void OnDetectionChanged(object sender, EventArgs e)
		{
			this.DetectionChanged?.Invoke(this, EventArgs.Empty);
		}

		private static ConfigEntry<T> Bind<T>(ConfigFile file, string section, string identifier, T defaultValue, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Bind(file, section, identifier, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		private static ConfigEntry<T> Bind<T>(ConfigFile file, string section, string identifier, T defaultValue, ConfigDescription description)
		{
			return file.Bind<T>(section, ReadableKey(identifier), defaultValue, description);
		}

		private static string ReadableKey(string identifier)
		{
			if (string.IsNullOrEmpty(identifier))
			{
				return identifier;
			}
			StringBuilder stringBuilder = new StringBuilder(identifier.Length + 4);
			stringBuilder.Append(char.ToUpperInvariant(identifier[0]));
			for (int i = 1; i < identifier.Length; i++)
			{
				char c = identifier[i];
				if (char.IsUpper(c))
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(char.ToLowerInvariant(c));
			}
			return stringBuilder.ToString();
		}

		private static BannerAnchor ParseAnchor(string value)
		{
			return value?.Trim() switch
			{
				"TopLeft" => BannerAnchor.TopLeft, 
				"TopRight" => BannerAnchor.TopRight, 
				"Left" => BannerAnchor.Left, 
				"Center" => BannerAnchor.Center, 
				"Right" => BannerAnchor.Right, 
				"BottomLeft" => BannerAnchor.BottomLeft, 
				"BottomCenter" => BannerAnchor.BottomCenter, 
				"BottomRight" => BannerAnchor.BottomRight, 
				_ => BannerAnchor.Center, 
			};
		}

		private static PositionUnit ParseUnit(string value)
		{
			if (value?.Trim() == "Pixels")
			{
				return PositionUnit.Pixels;
			}
			return PositionUnit.Percent;
		}
	}
	public enum PositionUnit
	{
		Percent,
		Pixels
	}
}