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