Decompiled source of OverheadIDs v1.0.0

OverheadIds.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using Mimic.Actors;
using OverheadIds;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyTitle("OverheadIds")]
[assembly: AssemblyProduct("OverheadIds")]
[assembly: AssemblyCompany("dsh")]
[assembly: MelonInfo(typeof(Core), "OverheadIds", "0.1.0", "dsh", null)]
[assembly: MelonGame("ReLUGames", "MIMESIS")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace OverheadIds
{
	public sealed class Core : MelonMod
	{
		public override void OnInitializeMelon()
		{
			ModPreferences.Initialize();
			MelonLogger.Msg("[OverheadIds] loaded");
		}

		public override void OnUpdate()
		{
			if ((Object)(object)Hub.s != (Object)null)
			{
				OverheadOverlay.Ensure();
			}
		}
	}
	internal enum DisplayMode
	{
		Always,
		TimeWindow,
		Never,
		OutdoorOnly
	}
	internal static class ModPreferences
	{
		private const string CategoryId = "OverheadIds";

		private const string CategoryDisplayName = "OverheadIds";

		private static MelonPreferences_Category? _category;

		private static MelonPreferences_Entry<bool>? _enabled;

		private static MelonPreferences_Entry<bool>? _verbose;

		private static MelonPreferences_Entry<string>? _mode;

		private static MelonPreferences_Entry<float>? _showUntilHour;

		private static MelonPreferences_Entry<bool>? _randomizeDummyId;

		private static MelonPreferences_Entry<bool>? _randomizeMimicId;

		private static MelonPreferences_Entry<bool>? _changeMimicId;

		private static MelonPreferences_Entry<float>? _mimicIdChangeSeconds;

		private static MelonPreferences_Entry<float>? _fontSize;

		private static MelonPreferences_Entry<float>? _headOffset;

		private static MelonPreferences_Entry<float>? _maxDistance;

		private static MelonPreferences_Entry<string>? _color;

		private static MelonPreferences_Entry<bool>? _occlusion;

		internal static bool Enabled => Entry(ref _enabled).Value;

		internal static bool Verbose => Entry(ref _verbose).Value;

		internal static DisplayMode Mode
		{
			get
			{
				if (!Enum.TryParse<DisplayMode>(Entry(ref _mode).Value, ignoreCase: true, out var result))
				{
					return DisplayMode.Always;
				}
				return result;
			}
		}

		internal static float ShowUntilHour => Mathf.Clamp(Entry(ref _showUntilHour).Value, 0f, 24f);

		internal static bool RandomizeDummyId => Entry(ref _randomizeDummyId).Value;

		internal static bool RandomizeMimicId => Entry(ref _randomizeMimicId).Value;

		internal static bool ChangeMimicId => Entry(ref _changeMimicId).Value;

		internal static float MimicIdChangeSeconds => Mathf.Clamp(Entry(ref _mimicIdChangeSeconds).Value, 5f, 3600f);

		internal static float FontSize => Mathf.Clamp(Entry(ref _fontSize).Value, 8f, 72f);

		internal static float HeadOffset => Mathf.Clamp(Entry(ref _headOffset).Value, 0.5f, 5f);

		internal static float MaxDistance => Mathf.Clamp(Entry(ref _maxDistance).Value, 1f, 200f);

		internal static Color TextColor
		{
			get
			{
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				if (!TryParseColor(Entry(ref _color).Value, out var color))
				{
					return Color.white;
				}
				return color;
			}
		}

		internal static bool OcclusionCheck => Entry(ref _occlusion).Value;

		internal static void Initialize()
		{
			if (_category == null)
			{
				_category = MelonPreferences.CreateCategory("OverheadIds", "OverheadIds");
				_enabled = CreateEntry("Enabled", defaultValue: true, "Enabled", "Show overhead player IDs.");
				_verbose = CreateEntry("Verbose", defaultValue: false, "Verbose logging", "Log overlay creation and each random ID assignment.");
				_mode = CreateEntry("Mode", "Always", "Display mode (Always / TimeWindow / Never)", "Always = always visible. TimeWindow = only while the in-game clock is before ShowUntilHour (a new area starts at 9:00). OutdoorOnly = hidden while the local player is indoors. Never = hidden.");
				_showUntilHour = CreateEntry("ShowUntilHour", 11f, "Time window: show until hour", "TimeWindow mode: show overhead IDs while the in-game time is earlier than this hour. 9:00 arrival + 2h = 11.");
				_randomizeDummyId = CreateEntry("RandomizeDummyId", defaultValue: true, "Randomize dummy IDs", "Players without a SteamID (admin dummy players) display a random real player's ID.");
				_randomizeMimicId = CreateEntry("RandomizeMimicId", defaultValue: true, "Randomize mimic IDs", "Mimics display a random real player's ID instead of their own name.");
				_changeMimicId = CreateEntry("ChangeMimicId", defaultValue: true, "Mimic ID changes over time", "Periodically re-roll the random ID shown for mimics.");
				_mimicIdChangeSeconds = CreateEntry("MimicIdChangeSeconds", 120f, "Mimic ID change interval (seconds)", "How often the random mimic ID is re-rolled when ChangeMimicId is on.");
				_fontSize = CreateEntry("FontSize", 22f, "Font size", "Overhead label font size.");
				_headOffset = CreateEntry("HeadOffset", 2f, "Head offset (meters)", "Label height above the actor's feet.");
				_maxDistance = CreateEntry("MaxDistance", 45f, "Max distance (meters)", "Hide labels farther than this from the camera.");
				_color = CreateEntry("Color", "#FFFFFF", "Text color", "Label color, #RRGGBB or #RRGGBBAA.");
				_occlusion = CreateEntry("OcclusionCheck", defaultValue: true, "Wall occlusion", "Hide a label when a wall blocks the line from the camera to the actor's head.");
			}
		}

		internal static void Log(string message)
		{
			if (Verbose)
			{
				MelonLogger.Msg(message);
			}
		}

		internal static bool TryParseColor(string? text, out Color color)
		{
			//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_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			color = Color.white;
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = text.Trim().TrimStart('#');
			if (text2.Length != 6 && text2.Length != 8)
			{
				return false;
			}
			if (!uint.TryParse(text2, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			color = ((text2.Length == 6) ? new Color((float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f, 1f) : new Color((float)((result >> 24) & 0xFF) / 255f, (float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f));
			return true;
		}

		private static MelonPreferences_Entry<T> Entry<T>(ref MelonPreferences_Entry<T>? field)
		{
			if (field == null)
			{
				throw new InvalidOperationException("ModPreferences.Initialize() must run first.");
			}
			return field;
		}

		private static MelonPreferences_Entry<T> CreateEntry<T>(string identifier, T defaultValue, string displayName, string? description = null)
		{
			if (_category == null)
			{
				throw new InvalidOperationException("Preference category not initialized.");
			}
			return _category.CreateEntry<T>(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null);
		}
	}
	internal sealed class OverheadOverlay : MonoBehaviour
	{
		private sealed class ActorState
		{
			public string Name = string.Empty;

			public float NextChange;

			public bool Assigned;
		}

		private readonly struct Candidate
		{
			public readonly ProtoActor Actor;

			public readonly string Name;

			public Candidate(ProtoActor actor, string name)
			{
				Actor = actor;
				Name = name;
			}
		}

		private static OverheadOverlay? _instance;

		private static FieldInfo? _pdataField;

		private readonly Dictionary<int, TextMeshProUGUI> _labels = new Dictionary<int, TextMeshProUGUI>();

		private readonly Dictionary<int, ActorState> _states = new Dictionary<int, ActorState>();

		private readonly HashSet<int> _visible = new HashSet<int>();

		private RectTransform _root;

		private string _poolSignature = string.Empty;

		internal static void Ensure()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("OverheadIdsOverlay");
				Object.DontDestroyOnLoad((Object)(object)val);
				Canvas obj = val.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 5000;
				_instance = val.AddComponent<OverheadOverlay>();
				_instance._root = (RectTransform)val.transform;
				ModPreferences.Log("[OverheadIds] overlay created");
			}
		}

		private void Update()
		{
			try
			{
				Refresh();
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[OverheadIds] refresh failed: " + ex.Message);
			}
		}

		private void Refresh()
		{
			if (!ModPreferences.Enabled || ModPreferences.Mode == DisplayMode.Never)
			{
				HideAll();
				return;
			}
			GameMainBase main = Hub.Main;
			if ((Object)(object)main == (Object)null)
			{
				HideAll();
				return;
			}
			GamePlayScene val = (GamePlayScene)(object)((main is GamePlayScene) ? main : null);
			if (val != null)
			{
				if (ModPreferences.Mode == DisplayMode.TimeWindow && val.GetWorldTime() >= ModPreferences.ShowUntilHour)
				{
					HideAll();
					return;
				}
				if (ModPreferences.Mode == DisplayMode.OutdoorOnly && IsLocalPlayerIndoors(main, val))
				{
					HideAll();
					return;
				}
			}
			Camera main2 = Camera.main;
			if ((Object)(object)main2 == (Object)null)
			{
				HideAll();
				return;
			}
			List<ProtoActor> allPlayers = main.GetAllPlayers();
			List<ProtoActor> allMimics = main.GetAllMimics();
			ProtoActor myAvatar = main.GetMyAvatar();
			List<Candidate> list = new List<Candidate>();
			foreach (ProtoActor item in allPlayers)
			{
				if (!((Object)(object)item == (Object)null) && !item.dead && !IsDummy(main, item) && (!((Object)(object)myAvatar != (Object)null) || item.ActorID != myAvatar.ActorID))
				{
					string text = NameOf(main, item);
					if (!string.IsNullOrEmpty(text))
					{
						list.Add(new Candidate(item, text));
					}
				}
			}
			string text2 = string.Join("|", list.Select((Candidate candidate) => candidate.Name).OrderBy<string, string>((string x) => x, StringComparer.Ordinal));
			bool poolChanged = text2 != _poolSignature;
			_poolSignature = text2;
			_visible.Clear();
			foreach (ProtoActor item2 in allPlayers)
			{
				if (!((Object)(object)item2 == (Object)null) && !item2.dead)
				{
					if (IsDummy(main, item2) && ModPreferences.RandomizeDummyId)
					{
						Show(item2, RandomName(item2, list, poolChanged, allowChange: false, weightedByDistance: false), main2);
					}
					else
					{
						Show(item2, NameOf(main, item2), main2);
					}
				}
			}
			foreach (ProtoActor item3 in allMimics)
			{
				if (!((Object)(object)item3 == (Object)null) && !item3.dead)
				{
					string name = (ModPreferences.RandomizeMimicId ? RandomName(item3, list, poolChanged, ModPreferences.ChangeMimicId, weightedByDistance: true) : NameOf(main, item3));
					Show(item3, name, main2);
				}
			}
			foreach (KeyValuePair<int, TextMeshProUGUI> label in _labels)
			{
				if (!_visible.Contains(label.Key) && (Object)(object)label.Value != (Object)null && ((Component)label.Value).gameObject.activeSelf)
				{
					((Component)label.Value).gameObject.SetActive(false);
				}
			}
		}

		private void Show(ProtoActor actor, string? name, Camera camera)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(name))
			{
				return;
			}
			Vector3 val = ((Component)actor).transform.position + Vector3.up * ModPreferences.HeadOffset;
			Vector3 val2 = camera.WorldToScreenPoint(val);
			if (val2.z <= 0f)
			{
				return;
			}
			float maxDistance = ModPreferences.MaxDistance;
			Vector3 val3 = ((Component)camera).transform.position - val;
			if (!(((Vector3)(ref val3)).sqrMagnitude > maxDistance * maxDistance) && (!ModPreferences.OcclusionCheck || !IsOccluded(camera, val)))
			{
				TextMeshProUGUI val4 = Label(actor.ActorID);
				if (((TMP_Text)val4).text != name)
				{
					((TMP_Text)val4).text = name;
				}
				if (!((Component)val4).gameObject.activeSelf)
				{
					((Component)val4).gameObject.SetActive(true);
				}
				((Transform)((TMP_Text)val4).rectTransform).position = new Vector3(val2.x, val2.y, 0f);
				_visible.Add(actor.ActorID);
			}
		}

		private TextMeshProUGUI Label(int actorId)
		{
			//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)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: 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_00e0: Unknown result type (might be due to invalid IL or missing references)
			if (_labels.TryGetValue(actorId, out TextMeshProUGUI value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			GameObject val = new GameObject("OverheadIdLabel", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent((Transform)(object)_root, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).font = TMP_Settings.defaultFontAsset;
			((TMP_Text)val2).fontSize = ModPreferences.FontSize;
			((Graphic)val2).color = ModPreferences.TextColor;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)514;
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)val2).overflowMode = (TextOverflowModes)0;
			RectTransform rectTransform = ((TMP_Text)val2).rectTransform;
			rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
			rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
			rectTransform.pivot = new Vector2(0.5f, 0.5f);
			rectTransform.sizeDelta = new Vector2(600f, 60f);
			_labels[actorId] = val2;
			return val2;
		}

		private void HideAll()
		{
			foreach (TextMeshProUGUI value in _labels.Values)
			{
				if ((Object)(object)value != (Object)null && ((Component)value).gameObject.activeSelf)
				{
					((Component)value).gameObject.SetActive(false);
				}
			}
		}

		private string RandomName(ProtoActor actor, List<Candidate> pool, bool poolChanged, bool allowChange, bool weightedByDistance)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			if (pool.Count == 0)
			{
				return NameOf(Hub.Main, actor) ?? "PLAYER";
			}
			if (!_states.TryGetValue(actor.ActorID, out ActorState value))
			{
				value = new ActorState();
				_states[actor.ActorID] = value;
			}
			float time = Time.time;
			bool flag = allowChange && ModPreferences.ChangeMimicId && time >= value.NextChange;
			if (!value.Assigned || poolChanged || flag)
			{
				value.Name = Pick(pool, ((Component)actor).transform.position, weightedByDistance);
				value.NextChange = time + ModPreferences.MimicIdChangeSeconds;
				value.Assigned = true;
				ModPreferences.Log($"[OverheadIds] actor {actor.ActorID} -> {value.Name}");
			}
			return value.Name;
		}

		private static string Pick(List<Candidate> pool, Vector3 origin, bool weightedByDistance)
		{
			//IL_003b: 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)
			if (pool.Count == 0)
			{
				return "PLAYER";
			}
			if (!weightedByDistance)
			{
				return pool[Random.Range(0, pool.Count)].Name;
			}
			float num = 0f;
			for (int i = 0; i < pool.Count; i++)
			{
				num += Weight(pool[i], origin);
			}
			if (num <= 0f)
			{
				return pool[Random.Range(0, pool.Count)].Name;
			}
			float num2 = Random.value * num;
			for (int j = 0; j < pool.Count; j++)
			{
				num2 -= Weight(pool[j], origin);
				if (num2 <= 0f)
				{
					return pool[j].Name;
				}
			}
			return pool[pool.Count - 1].Name;
		}

		private static float Weight(Candidate candidate, Vector3 origin)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)candidate.Actor == (Object)null)
			{
				return 0f;
			}
			float num = Vector3.Distance(origin, ((Component)candidate.Actor).transform.position);
			return 1f / (1f + num * num);
		}

		private static bool IsLocalPlayerIndoors(GameMainBase main, GamePlayScene scene)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ProtoActor myAvatar = main.GetMyAvatar();
				if ((Object)(object)myAvatar == (Object)null)
				{
					return false;
				}
				Vector3 position = ((Component)myAvatar).transform.position;
				bool flag = default(bool);
				return scene.TryCheckPosIsIndoor(ref position, ref flag) && flag;
			}
			catch
			{
				return false;
			}
		}

		private static bool IsOccluded(Camera camera, Vector3 head)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)camera).transform.position;
			Vector3 val = head - position;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude <= 0.05f)
			{
				return false;
			}
			RaycastHit val2 = default(RaycastHit);
			return Physics.Raycast(position, val / magnitude, ref val2, magnitude - 0.1f, Hub.AllWallsLayerMask, (QueryTriggerInteraction)1);
		}

		private static string? NameOf(GameMainBase? main, ProtoActor actor)
		{
			try
			{
				if ((Object)(object)main != (Object)null)
				{
					string text = main.ResolveNickName(actor, actor.nickName);
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(actor.nickName))
			{
				return actor.nickName;
			}
			return null;
		}

		private static bool IsDummy(GameMainBase main, ProtoActor actor)
		{
			try
			{
				ProtoActor myAvatar = main.GetMyAvatar();
				if ((Object)(object)myAvatar != (Object)null && myAvatar.UID == actor.UID)
				{
					return false;
				}
				if ((object)_pdataField == null)
				{
					_pdataField = AccessTools.Field(typeof(Hub), "pdata");
				}
				object? obj = _pdataField?.GetValue(Hub.s);
				Dictionary<long, ulong> dictionary = ((PersistentData)(((obj is PersistentData) ? obj : null)?)).actorUIDToSteamID;
				if (dictionary == null || dictionary.Count == 0)
				{
					return false;
				}
				return !dictionary.ContainsKey(actor.UID);
			}
			catch
			{
				return false;
			}
		}
	}
}