Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CreatureStarAlert v0.2.0
plugins/CreatureStarAlert.dll
Decompiled a week agousing 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 } }