Decompiled source of MonsterPathVisualisation v0.4.4

MonsterPathVisualisation.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppTMPro;
using Kp0hyc.Labyrinthine.Shared;
using MelonLoader;
using MelonLoader.Preferences;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(MonsterPathVisualisation), "Monster Path Visualisation", "0.4.4", "kp0hyc", null)]
[assembly: MelonGame("Valko Game Studios", "Labyrinthine")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("MonsterPathVisualisation")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MonsterPathVisualisation")]
[assembly: AssemblyTitle("MonsterPathVisualisation")]
[assembly: AssemblyVersion("1.0.0.0")]
public class MonsterPathVisualisation : MelonMod
{
	private sealed class NameListSecondaryPanelState
	{
		public readonly int InstanceId;

		public readonly Vector2 SizeDelta;

		public readonly Vector2 AnchoredPosition;

		public readonly Vector3 LocalPosition;

		public readonly Vector2 Pivot;

		public NameListSecondaryPanelState(GameObject panel, RectTransform rectTransform)
		{
			//IL_0032: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			InstanceId = ((!((Object)(object)panel == (Object)null)) ? ((Object)panel).GetInstanceID() : 0);
			SizeDelta = (Vector2)(((Object)(object)rectTransform == (Object)null) ? default(Vector2) : rectTransform.sizeDelta);
			AnchoredPosition = (Vector2)(((Object)(object)rectTransform == (Object)null) ? default(Vector2) : rectTransform.anchoredPosition);
			LocalPosition = (Vector3)(((Object)(object)rectTransform == (Object)null) ? default(Vector3) : ((Transform)rectTransform).localPosition);
			Pivot = (Vector2)(((Object)(object)rectTransform == (Object)null) ? new Vector2(0.5f, 0.5f) : rectTransform.pivot);
		}

		public void Restore(RectTransform rectTransform)
		{
			//IL_000c: 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)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)rectTransform == (Object)null))
			{
				rectTransform.sizeDelta = SizeDelta;
				rectTransform.anchoredPosition = AnchoredPosition;
				((Transform)rectTransform).localPosition = LocalPosition;
			}
		}
	}

	private sealed class NameListRowTransformState
	{
		public readonly Vector2 AnchoredPosition;

		public readonly Vector3 LocalPosition;

		public readonly Vector2 AnchorMin;

		public readonly Vector2 AnchorMax;

		public readonly Vector2 Pivot;

		public readonly Vector2 SizeDelta;

		public readonly Vector3 LocalScale;

		public readonly Quaternion LocalRotation;

		public NameListRowTransformState(RectTransform rectTransform)
		{
			//IL_001a: 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)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0040: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: 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_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: 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_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: 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)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			AnchoredPosition = (Vector2)(((Object)(object)rectTransform == (Object)null) ? default(Vector2) : rectTransform.anchoredPosition);
			LocalPosition = (Vector3)(((Object)(object)rectTransform == (Object)null) ? default(Vector3) : ((Transform)rectTransform).localPosition);
			AnchorMin = (Vector2)(((Object)(object)rectTransform == (Object)null) ? default(Vector2) : rectTransform.anchorMin);
			AnchorMax = (Vector2)(((Object)(object)rectTransform == (Object)null) ? default(Vector2) : rectTransform.anchorMax);
			Pivot = (Vector2)(((Object)(object)rectTransform == (Object)null) ? new Vector2(0.5f, 0.5f) : rectTransform.pivot);
			SizeDelta = (Vector2)(((Object)(object)rectTransform == (Object)null) ? default(Vector2) : rectTransform.sizeDelta);
			LocalScale = (Vector3)(((Object)(object)rectTransform == (Object)null) ? new Vector3(1f, 1f, 1f) : ((Transform)rectTransform).localScale);
			LocalRotation = (((Object)(object)rectTransform == (Object)null) ? Quaternion.identity : ((Transform)rectTransform).localRotation);
		}
	}

	private sealed class ExtraNameListRow
	{
		public readonly GameObject Element;

		public readonly object Text;

		public ExtraNameListRow(GameObject element, object text)
		{
			Element = element;
			Text = text;
		}
	}

	private readonly struct PathEntry
	{
		public readonly string Key;

		public readonly string Label;

		public readonly TrailColor Color;

		public PathEntry(string key, string label, TrailColor color)
		{
			Key = key;
			Label = label;
			Color = color;
		}
	}

	private readonly struct TrailColor
	{
		public readonly byte R;

		public readonly byte G;

		public readonly byte B;

		public readonly byte A;

		public TrailColor(byte r, byte g, byte b, byte a)
		{
			R = r;
			G = g;
			B = b;
			A = a;
		}
	}

	private sealed class MonsterIcon
	{
		public readonly string Key;

		public readonly int Width;

		public readonly int Height;

		public readonly TrailColor[] Pixels;

		public MonsterIcon(string key, int width, int height, TrailColor[] pixels)
		{
			Key = key;
			Width = width;
			Height = height;
			Pixels = pixels;
		}
	}

	private readonly struct MonsterIconDrawRequest
	{
		public readonly int PixelX;

		public readonly int PixelY;

		public readonly int Number;

		public readonly TrailColor Color;

		public readonly string Label;

		public MonsterIconDrawRequest(int pixelX, int pixelY, int number, TrailColor color, string label)
		{
			PixelX = pixelX;
			PixelY = pixelY;
			Number = number;
			Color = color;
			Label = label;
		}
	}

	private readonly struct PlayerIconDrawRequest
	{
		public readonly int PixelX;

		public readonly int PixelY;

		public readonly string Key;

		public readonly TrailColor Color;

		public PlayerIconDrawRequest(int pixelX, int pixelY, string key, TrailColor color)
		{
			PixelX = pixelX;
			PixelY = pixelY;
			Key = key;
			Color = color;
		}
	}

	private readonly struct WorldPoint
	{
		public readonly float X;

		public readonly float Z;

		public readonly float Time;

		public WorldPoint(float x, float z, float time = 0f)
		{
			X = x;
			Z = z;
			Time = time;
		}
	}

	private sealed class MonsterTrail
	{
		private readonly List<WorldPoint> _points = new List<WorldPoint>();

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

		private readonly List<int> _deathIndexes = new List<int>();

		private bool _wasDead;

		public readonly int ColorIndex;

		public string Label { get; private set; }

		public int Count => _points.Count;

		public bool HasDeaths => _deathIndexes.Count > 0;

		public float DurationSeconds
		{
			get
			{
				if (_points.Count != 0)
				{
					return _points[_points.Count - 1].Time;
				}
				return 0f;
			}
		}

		public MonsterTrail(string label, int colorIndex)
		{
			Label = label;
			ColorIndex = colorIndex;
		}

		public void UpdateLabelIfBetter(string label)
		{
			if (!string.IsNullOrWhiteSpace(label) && (string.IsNullOrWhiteSpace(Label) || string.Equals(Label, "Player", StringComparison.OrdinalIgnoreCase) || string.Equals(Label, "Monster", StringComparison.OrdinalIgnoreCase) || string.Equals(Label, "Local Player", StringComparison.OrdinalIgnoreCase)))
			{
				Label = label;
			}
		}

		public void Add(float x, float z, float elapsedSeconds, float minTeleportDistance)
		{
			WorldPoint worldPoint = new WorldPoint(x, z, elapsedSeconds);
			if (_points.Count > 0)
			{
				WorldPoint a = _points[_points.Count - 1];
				float num = Distance(a, worldPoint);
				if (num < 0.2f && Math.Abs(elapsedSeconds - a.Time) < 0.001f)
				{
					return;
				}
				if (num > minTeleportDistance)
				{
					_segmentStarts.Add(_points.Count);
				}
			}
			_points.Add(worldPoint);
		}

		public bool UpdateDeadState(bool isDead)
		{
			if (isDead && !_wasDead && _points.Count > 0)
			{
				int num = _points.Count - 1;
				if (_deathIndexes.Count == 0 || _deathIndexes[_deathIndexes.Count - 1] != num)
				{
					_deathIndexes.Add(num);
				}
				_wasDead = true;
				return true;
			}
			_wasDead = isDead;
			return false;
		}

		public void GetReplayPoints(float normalizedTime, float replayDurationSeconds, float maxStepWorldSpace, List<WorldPoint> output)
		{
			output.Clear();
			if (_points.Count == 0)
			{
				return;
			}
			float targetTime = ValueConversion.Clamp01(normalizedTime) * Math.Max(0.001f, replayDurationSeconds);
			int lastIndexAtOrBefore = GetLastIndexAtOrBefore(targetTime);
			if (lastIndexAtOrBefore < 0)
			{
				return;
			}
			output.Add(_points[0]);
			for (int i = 1; i <= lastIndexAtOrBefore; i++)
			{
				WorldPoint previous = _points[i - 1];
				WorldPoint worldPoint = _points[i];
				if (_segmentStarts.Contains(i))
				{
					output.Add(worldPoint);
				}
				else
				{
					AddInterpolated(previous, worldPoint, maxStepWorldSpace, output);
				}
			}
		}

		public bool TryGetReplayPoint(float normalizedTime, float replayDurationSeconds, out WorldPoint point)
		{
			point = default(WorldPoint);
			if (_points.Count == 0)
			{
				return false;
			}
			float num = ValueConversion.Clamp01(normalizedTime) * Math.Max(0.001f, replayDurationSeconds);
			int lastIndexAtOrBefore = GetLastIndexAtOrBefore(num);
			if (lastIndexAtOrBefore < 0)
			{
				return false;
			}
			if (lastIndexAtOrBefore >= _points.Count - 1)
			{
				point = _points[lastIndexAtOrBefore];
				return true;
			}
			int num2 = lastIndexAtOrBefore + 1;
			if (_segmentStarts.Contains(num2))
			{
				point = _points[lastIndexAtOrBefore];
				return true;
			}
			WorldPoint worldPoint = _points[lastIndexAtOrBefore];
			WorldPoint worldPoint2 = _points[num2];
			float num3 = worldPoint2.Time - worldPoint.Time;
			if (num3 <= 0.001f)
			{
				point = worldPoint;
				return true;
			}
			float num4 = ValueConversion.Clamp01((num - worldPoint.Time) / num3);
			point = new WorldPoint(worldPoint.X + (worldPoint2.X - worldPoint.X) * num4, worldPoint.Z + (worldPoint2.Z - worldPoint.Z) * num4, num);
			return true;
		}

		public bool TryGetVisibleDeathPoint(float normalizedTime, float replayDurationSeconds, out WorldPoint point, out int deathIndex)
		{
			point = default(WorldPoint);
			deathIndex = -1;
			if (_points.Count == 0 || _deathIndexes.Count == 0)
			{
				return false;
			}
			float targetTime = ValueConversion.Clamp01(normalizedTime) * Math.Max(0.001f, replayDurationSeconds);
			int lastIndexAtOrBefore = GetLastIndexAtOrBefore(targetTime);
			if (lastIndexAtOrBefore < 0)
			{
				return false;
			}
			for (int i = 0; i < _deathIndexes.Count; i++)
			{
				int num = _deathIndexes[i];
				if (num <= lastIndexAtOrBefore)
				{
					deathIndex = i;
					point = _points[Math.Max(0, Math.Min(_points.Count - 1, num))];
				}
			}
			return deathIndex >= 0;
		}

		public void GetVisibleDeathIndexes(float normalizedTime, float replayDurationSeconds, List<int> output)
		{
			output.Clear();
			if (_points.Count == 0 || _deathIndexes.Count == 0)
			{
				return;
			}
			float targetTime = ValueConversion.Clamp01(normalizedTime) * Math.Max(0.001f, replayDurationSeconds);
			int lastIndexAtOrBefore = GetLastIndexAtOrBefore(targetTime);
			if (lastIndexAtOrBefore < 0)
			{
				return;
			}
			for (int i = 0; i < _deathIndexes.Count; i++)
			{
				if (_deathIndexes[i] <= lastIndexAtOrBefore)
				{
					output.Add(i);
				}
			}
		}

		private int GetLastIndexAtOrBefore(float targetTime)
		{
			int result = -1;
			int num = 0;
			int num2 = _points.Count - 1;
			while (num <= num2)
			{
				int num3 = num + (num2 - num) / 2;
				if (_points[num3].Time <= targetTime)
				{
					result = num3;
					num = num3 + 1;
				}
				else
				{
					num2 = num3 - 1;
				}
			}
			return result;
		}

		private static void AddInterpolated(WorldPoint previous, WorldPoint current, float maxStepWorldSpace, List<WorldPoint> output)
		{
			float num = Distance(previous, current);
			if (!(num <= 0.001f))
			{
				int num2 = Math.Max(1, (int)Math.Ceiling(num / Math.Max(0.25f, maxStepWorldSpace)));
				for (int i = 1; i <= num2; i++)
				{
					float num3 = (float)i / (float)num2;
					output.Add(new WorldPoint(previous.X + (current.X - previous.X) * num3, previous.Z + (current.Z - previous.Z) * num3, previous.Time + (current.Time - previous.Time) * num3));
				}
			}
		}

		private static float Distance(WorldPoint a, WorldPoint b)
		{
			float num = a.X - b.X;
			float num2 = a.Z - b.Z;
			return (float)Math.Sqrt(num * num + num2 * num2);
		}
	}

	private const string HarmonyId = "kp0hyc.monsterpathvisualisation";

	private const string PrefCategoryName = "MonsterPathVisualisation";

	private const string MonsterObjectNamesResourceName = "MonsterPathVisualisation.MonsterNames.json";

	private const int BuiltInNameListSlotCount = 8;

	private const float PlaybackLoopSeconds = 5f;

	private const float PlaybackEndHoldSeconds = 1.5f;

	private const float SaveIntervalSeconds = 0.45f;

	private const float MinTeleportDistanceWorld = 35f;

	private const int TrailExtraPixels = 0;

	private const int MinimumFallbackPlayerTrailSamples = 30;

	private const int NameListSecondaryLabelDownshiftRows = 0;

	private static readonly Dictionary<string, MonsterTrail> s_trails = new Dictionary<string, MonsterTrail>();

	private static readonly Dictionary<string, MonsterTrail> s_playerTrails = new Dictionary<string, MonsterTrail>();

	private static readonly Dictionary<string, MonsterIcon> s_playerIcons = new Dictionary<string, MonsterIcon>(StringComparer.Ordinal);

	private static readonly Dictionary<string, DateTime> s_playerIconRetryUtc = new Dictionary<string, DateTime>(StringComparer.Ordinal);

	private static readonly List<object> s_monsterLookupBuffer = new List<object>();

	private static readonly List<object> s_playerLookupBuffer = new List<object>();

	private static readonly List<WorldPoint> s_replayBuffer = new List<WorldPoint>();

	private static readonly List<int> s_replayDeathIndexes = new List<int>();

	private static readonly List<PlayerIconDrawRequest> s_playerIconDrawRequests = new List<PlayerIconDrawRequest>();

	private static readonly List<PathEntry> s_playerEntries = new List<PathEntry>();

	private static readonly List<PathEntry> s_monsterEntries = new List<PathEntry>();

	private static readonly List<PathEntry> s_nameListEntries = new List<PathEntry>();

	private static readonly List<ExtraNameListRow> s_extraNameListRows = new List<ExtraNameListRow>();

	private static readonly Dictionary<int, NameListRowTransformState> s_nameListRowTransformStates = new Dictionary<int, NameListRowTransformState>();

	private static readonly Dictionary<string, DateTime> s_nextNameListDiagnosticsBySignature = new Dictionary<string, DateTime>(StringComparer.Ordinal);

	private static readonly Dictionary<string, DateTime> s_nextRectTransformDiagnosticsBySignature = new Dictionary<string, DateTime>(StringComparer.Ordinal);

	private static readonly Dictionary<string, DateTime> s_nextPlayerTrailDiagnosticsBySignature = new Dictionary<string, DateTime>(StringComparer.Ordinal);

	private static readonly List<object> s_cachedMonsterGameObjectSourceObjects = new List<object>();

	private static readonly Dictionary<string, bool> s_pathVisibility = new Dictionary<string, bool>();

	private static readonly Dictionary<string, MonsterIcon> s_monsterIcons = new Dictionary<string, MonsterIcon>(StringComparer.OrdinalIgnoreCase);

	private static Dictionary<string, string> s_knownMonsterObjectNameMap;

	private static readonly Dictionary<string, string> s_monsterIconAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		{ "ashoni", "oni" },
		{ "paleoni", "oni" },
		{ "palewitch", "witch" },
		{ "blackpigman", "pigman" },
		{ "smolderingwickerman", "wickerman" },
		{ "smoulderingwickerman", "wickerman" },
		{ "trenchstalker", "stalker" },
		{ "cryptstalker", "stalker" },
		{ "fastzombie", "zombie" },
		{ "slowzombie", "zombie" },
		{ "crawlingzombie", "zombie" },
		{ "bride", "bridelady" },
		{ "lady", "bridelady" }
	};

	private static readonly TrailColor[] s_palette = new TrailColor[6]
	{
		new TrailColor(byte.MaxValue, 62, 42, 178),
		new TrailColor(byte.MaxValue, 151, 32, 178),
		new TrailColor(224, 46, byte.MaxValue, 178),
		new TrailColor(72, 216, byte.MaxValue, 178),
		new TrailColor(byte.MaxValue, 230, 58, 178),
		new TrailColor(116, byte.MaxValue, 92, 178)
	};

	private static MelonPreferences_Entry<bool> s_verboseLogs;

	private static MelonPreferences_Entry<bool> s_debugPlayerPath;

	private static DateTime s_nextRecordUtc = DateTime.MinValue;

	private static DateTime s_recordingStartUtc = DateTime.MinValue;

	private static DateTime s_nextReflectionWarningUtc = DateTime.MinValue;

	private static DateTime s_nextDrawWarningUtc = DateTime.MinValue;

	private static int s_nextColorIndex;

	private static int s_lastLoggedActiveCount = -1;

	private static int s_lastLoggedTrailCount = -1;

	private static bool s_loggedFirstDraw;

	private static bool s_paused;

	private static bool s_showFullPath = true;

	private static bool s_monsterIconsEnabled = true;

	private static float s_scrubTime;

	private static float s_playbackSpeed = 1f;

	private static DateTime s_lastPlaybackUpdateUtc = DateTime.MinValue;

	private static DateTime s_nextPlaybackRedrawUtc = DateTime.MinValue;

	private static DateTime s_playbackEndHoldUntilUtc = DateTime.MinValue;

	private static DateTime s_lastEndMapDrawUtc = DateTime.MinValue;

	private static object s_lastUiInstance;

	private static object s_extraNameListUi;

	private static NameListSecondaryPanelState s_nameListSecondaryPanelState;

	private static object s_lastTexture;

	private static object s_lastPathRecorders;

	private static string s_lastPathRecordersSource;

	private static object s_lastTilePositionOffset;

	private static float s_lastPixelsPerTile;

	private static bool s_inRealMazeScene;

	private static string s_currentRealMazeSceneName;

	private static DateTime s_nextMonsterGameObjectSourceScanUtc = DateTime.MinValue;

	private static int s_fastMonsterGameObjectSourceScansRemaining;

	private static string s_lastMonsterGameObjectSourceDumpSignature;

	private static DateTime s_nextMonsterGameObjectSourceDumpUtc = DateTime.MinValue;

	private static Type s_pathVisualisationUiType;

	private static Type s_pathVisualisationManagerType;

	private static Type s_mazeGeneratorType;

	private static Type s_playerNetworkSyncType;

	private static Type s_gameManagerType;

	private static Type s_endScreenUiType;

	private static Type s_aiControllerType;

	private static Type s_monsterNetworkSyncType;

	private static Type s_color32Type;

	private static Type s_rectType;

	private static Type s_colorType;

	private static Type s_guiType;

	private static Type s_texture2DType;

	private static Type s_textureType;

	private static Type s_screenType;

	private static Type s_inputType;

	private static MethodInfo s_addPlayersToTextureMethod;

	private static MethodInfo s_addPlayerDotMethod;

	private static MethodInfo s_guiDrawTexture;

	private static MethodInfo s_guiLabel;

	private static MethodInfo s_guiButton;

	private static MethodInfo s_guiToggle;

	private static MethodInfo s_guiHorizontalSlider;

	private static MethodInfo s_textureSetPixels;

	private static MethodInfo s_textureSetPixel;

	private static MethodInfo s_textureApply;

	private static MethodInfo s_inputGetMouseButtonDown;

	private static MethodInfo s_clearTextureCoroutineMethod;

	private static PropertyInfo s_guiColorProperty;

	private static PropertyInfo s_guiSkinProperty;

	private static PropertyInfo s_whiteTextureProperty;

	private static PropertyInfo s_screenWidthProperty;

	private static PropertyInfo s_screenHeightProperty;

	private static PropertyInfo s_inputMousePositionProperty;

	private static PropertyInfo s_textureWidthProperty;

	private static PropertyInfo s_textureHeightProperty;

	private static ConstructorInfo s_color32Ctor;

	private static ConstructorInfo s_rectCtor;

	private static ConstructorInfo s_colorCtor;

	private static object s_whiteTexture;

	private static object s_clearColor;

	private static Array s_clearPixels;

	private static int s_clearPixelsWidth;

	private static int s_clearPixelsHeight;

	private static bool s_unityReflectionReady;

	private static bool s_textureSetPixelsFailed;

	private static bool s_clearWarningLogged;

	private static bool s_allowTextureClearCoroutine;

	private static bool s_internalGamePlayerDraw;

	private static Texture2D s_batchTexture;

	private static Il2CppStructArray<Color32> s_batchPixels;

	private static int s_batchWidth;

	private static int s_batchHeight;

	private static bool s_batchDrawActive;

	private static DateTime s_nextBatchWarningUtc = DateTime.MinValue;

	private static DateTime s_nextGuiWarningUtc = DateTime.MinValue;

	private static DateTime s_nextPlayerDebugLogUtc = DateTime.MinValue;

	private static DateTime s_nextPlayerRenderStatusUtc = DateTime.MinValue;

	private static DateTime s_nextMonsterDiagnosticsUtc = DateTime.MinValue;

	private static DateTime s_nextPlayerNameDiagnosticsUtc = DateTime.MinValue;

	private static DateTime s_nextEndMapDiagnosticsUtc = DateTime.MinValue;

	private static DateTime s_nextMonsterIconDebugUtc = DateTime.MinValue;

	private static DateTime s_nextPlayerIconDebugUtc = DateTime.MinValue;

	private static DateTime s_nextNameListStructureDiagnosticsUtc = DateTime.MinValue;

	private static DateTime s_nextNameListPlacementDiagnosticsUtc = DateTime.MinValue;

	private static DateTime s_nextRectTransformDiagnosticsUtc = DateTime.MinValue;

	private static string s_lastPlayerRenderStatus;

	private static string s_lastMonsterDiagnosticsStatus;

	private static string s_lastPlayerNameDiagnosticsStatus;

	private static string s_lastEndMapDiagnosticsStatus;

	private static string s_lastNameListStructureDiagnosticsStatus;

	private static string s_lastNameListPlacementDiagnosticsStatus;

	private static float s_cachedNameListRowSpacing;

	private static string s_cachedNameListRowSpacingSource;

	private static int s_cachedNameListRowSpacingPanelInstanceId;

	private static int s_lastLoggedPlayerTrailCount = -1;

	private static int s_lastMonsterIconDrawCount;

	private static bool s_monsterIconsLoaded;

	private static bool s_scaledImguiFontActive;

	private static int s_savedGuiButtonFontSize;

	private static int s_savedGuiLabelFontSize;

	private static int s_savedGuiToggleFontSize;

	private static void DrawControlPanel()
	{
		float scale = UiScale();
		BeginScaledImguiFont(scale);
		try
		{
			float height = S(84f, scale);
			GetControlPanelLayout(height, out var x, out var y, out var width);
			DrawSolid(x, y, width, height, Color(0.02f, 0.02f, 0.02f, 0.58f));
			if (Button(x + S(10f, scale), y + S(8f, scale), S(86f, scale), S(22f, scale), s_paused ? "Resume" : "Pause"))
			{
				s_paused = !s_paused;
				ResetPlaybackClock();
				RedrawLastTexture();
			}
			if (Button(x + S(104f, scale), y + S(8f, scale), S(124f, scale), S(22f, scale), s_showFullPath ? "Full path" : "Current dots"))
			{
				s_showFullPath = !s_showFullPath;
				RedrawLastTexture();
			}
			if (Button(x + S(238f, scale), y + S(8f, scale), S(96f, scale), S(22f, scale), ShouldUseMonsterIcons() ? "Icons on" : "Icons off"))
			{
				bool flag = (s_monsterIconsEnabled = !ShouldUseMonsterIcons());
				s_lastMonsterIconDrawCount = 0;
				LogMonsterIconStatus("toggle:" + flag, "enabled=" + flag + " fullPath=" + s_showFullPath + " trails=" + s_trails.Count);
				RedrawLastTexture();
			}
			Label(x + S(10f, scale), y + S(36f, scale), S(48f, scale), S(20f, scale), "speed");
			DrawPlaybackSpeedButton(x + S(60f, scale), y + S(34f, scale), S(46f, scale), S(22f, scale), 1f, "1x");
			DrawPlaybackSpeedButton(x + S(112f, scale), y + S(34f, scale), S(56f, scale), S(22f, scale), 0.5f, "0.5x");
			DrawPlaybackSpeedButton(x + S(174f, scale), y + S(34f, scale), S(66f, scale), S(22f, scale), 0.25f, "0.25x");
			DrawPlaybackSpeedButton(x + S(246f, scale), y + S(34f, scale), S(56f, scale), S(22f, scale), 0.1f, "0.1x");
			Label(x + S(314f, scale), y + S(36f, scale), S(92f, scale), S(20f, scale), "time " + (int)(ValueConversion.Clamp01(s_scrubTime) * 100f) + "%");
			float num = HorizontalSlider(x + S(10f, scale), y + S(60f, scale), width - S(20f, scale), S(18f, scale), ValueConversion.Clamp01(s_scrubTime), 0f, 1f);
			if (Math.Abs(num - s_scrubTime) > 0.001f)
			{
				s_scrubTime = ValueConversion.Clamp01(num);
				s_paused = true;
				ResetPlaybackClock();
				RedrawLastTexture();
			}
			DrawNameList("ongui");
		}
		finally
		{
			EndScaledImguiFont();
		}
	}

	private static bool ShouldUseMonsterIcons()
	{
		return s_monsterIconsEnabled;
	}

	private static void DrawPlaybackSpeedButton(float x, float y, float width, float height, float speed, string label)
	{
		bool flag = Math.Abs(s_playbackSpeed - speed) < 0.001f;
		if (Button(x, y, width, height, flag ? ("[" + label + "]") : label))
		{
			SetPlaybackSpeed(speed);
		}
	}

	private static void SetPlaybackSpeed(float speed)
	{
		s_playbackSpeed = ValueConversion.Clamp(speed, 0.05f, 4f);
		ResetPlaybackClock();
	}

	private static void ResetPlaybackClock()
	{
		s_lastPlaybackUpdateUtc = DateTime.MinValue;
		s_nextPlaybackRedrawUtc = DateTime.MinValue;
		s_playbackEndHoldUntilUtc = DateTime.MinValue;
	}

	private static void GetControlPanelLayout(float height, out float x, out float y, out float width)
	{
		float scale = UiScale();
		x = S(70f, scale);
		y = S(12f, scale);
		width = Math.Min(500f, (float)ScreenWidth() - x * 2f);
		if (TryGetPathImageGuiRect(s_lastUiInstance, out var x2, out var y2, out var width2, out var _))
		{
			float val = S(500f, scale);
			float val2 = S(460f, scale);
			float num = Math.Min(val, Math.Max(val2, width2));
			float value = x2 + (width2 - num) * 0.5f;
			float num2 = y2 - height - S(8f, scale);
			if (!(num2 < S(8f, scale)))
			{
				width = Math.Min(num, (float)ScreenWidth() - S(24f, scale));
				x = ValueConversion.Clamp(value, S(12f, scale), Math.Max(S(12f, scale), (float)ScreenWidth() - width - S(12f, scale)));
				y = num2;
			}
		}
	}

	private static bool TryGetPathImageGuiRect(object ui, out float x, out float y, out float width, out float height)
	{
		x = 0f;
		y = 0f;
		width = 0f;
		height = 0f;
		try
		{
			object fieldOrProp = Il2CppReflection.GetFieldOrProp(ui, "pathImage");
			RawImage val = (RawImage)((fieldOrProp is RawImage) ? fieldOrProp : null);
			RectTransform val2 = (((Object)(object)val == (Object)null) ? null : ((Graphic)val).rectTransform);
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			object fieldOrProp2 = Il2CppReflection.GetFieldOrProp(val2, "rect");
			object fieldOrProp3 = Il2CppReflection.GetFieldOrProp(val2, "position");
			object fieldOrProp4 = Il2CppReflection.GetFieldOrProp(val2, "pivot");
			object fieldOrProp5 = Il2CppReflection.GetFieldOrProp(val2, "lossyScale");
			if (!ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp2, "width"), out var result) || !ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp2, "height"), out var result2) || !ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp3, "x"), out var result3) || !ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp3, "y"), out var result4) || !ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp4, "x"), out var result5) || !ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp4, "y"), out var result6))
			{
				return false;
			}
			float result7;
			float num = (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp5, "x"), out result7) ? Math.Abs(result7) : 1f);
			float result8;
			float num2 = (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp5, "y"), out result8) ? Math.Abs(result8) : 1f);
			float num3 = Math.Abs(result * num);
			float num4 = Math.Abs(result2 * num2);
			float num5 = result3 - result5 * num3;
			float num6 = num5 + num3;
			float num7 = result4 - result6 * num4;
			float num8 = num7 + num4;
			if (num6 <= num5 || num8 <= num7)
			{
				return false;
			}
			x = num5;
			y = (float)ScreenHeight() - num8;
			width = num6 - num5;
			height = num8 - num7;
			return width > 40f && height > 40f;
		}
		catch
		{
			return false;
		}
	}

	private static bool TryGetTextureDimensions(object texture, out int width, out int height)
	{
		width = 0;
		height = 0;
		if (texture == null)
		{
			return false;
		}
		try
		{
			Type type = texture.GetType();
			if ((object)s_textureWidthProperty == null)
			{
				s_textureWidthProperty = type.GetProperty("width", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			if ((object)s_textureHeightProperty == null)
			{
				s_textureHeightProperty = type.GetProperty("height", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			return ValueConversion.TryConvertInt(s_textureWidthProperty?.GetValue(texture), out width) && ValueConversion.TryConvertInt(s_textureHeightProperty?.GetValue(texture), out height) && width > 0 && height > 0;
		}
		catch
		{
			return false;
		}
	}

	public override void OnInitializeMelon()
	{
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Expected O, but got Unknown
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Expected O, but got Unknown
		//IL_00de: Expected O, but got Unknown
		//IL_0150: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Expected O, but got Unknown
		try
		{
			MelonPreferences_Category obj = MelonPreferences.CreateCategory("MonsterPathVisualisation");
			s_verboseLogs = obj.CreateEntry<bool>("VerboseLogs", false, "Log detailed MonsterPathVisualisation diagnostics.", (string)null, false, false, (ValueValidator)null, (string)null);
			s_debugPlayerPath = obj.CreateEntry<bool>("DebugPlayerPath", false, "Log focused player path diagnostics.", (string)null, false, false, (ValueValidator)null, (string)null);
			Harmony val = new Harmony("kp0hyc.monsterpathvisualisation");
			Type pathVisualisationUiType = GetPathVisualisationUiType();
			MethodInfo methodInfo = (s_addPlayersToTextureMethod = pathVisualisationUiType?.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "AddPlayersToTexture" && m.GetParameters().Length == 5));
			if (methodInfo == null)
			{
				MelonLogger.Warning("Could not find PathVisualisationUI.AddPlayersToTexture; monster paths will not draw on the end map.");
			}
			else
			{
				val.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(MonsterPathVisualisation).GetMethod("AddPlayersToTexturePrefix", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(MonsterPathVisualisation).GetMethod("AddPlayersToTexturePostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				LogVerbose("Patched PathVisualisationUI.AddPlayersToTexture.");
			}
			MethodInfo methodInfo2 = pathVisualisationUiType?.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((Type t) => t.Name.IndexOf("ClearTextureCoroutine", StringComparison.OrdinalIgnoreCase) >= 0)?.GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (methodInfo2 != null)
			{
				val.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(MonsterPathVisualisation).GetMethod("ClearTextureCoroutineMoveNextPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				LogVerbose("Patched PathVisualisationUI.ClearTextureCoroutine.MoveNext.");
			}
			LogVerbose("MonsterPathVisualisation initialized.");
		}
		catch (Exception ex)
		{
			MelonLogger.Error("Init failed: " + ex);
		}
	}

	public override void OnSceneWasLoaded(int buildIndex, string sceneName)
	{
		if (string.Equals(sceneName, GetActiveSceneName(), StringComparison.OrdinalIgnoreCase))
		{
			ResetRecording();
			UpdateRealMazeSceneState(sceneName);
		}
	}

	public override void OnUpdate()
	{
		try
		{
			DateTime utcNow = DateTime.UtcNow;
			DriveEndMapPlayback(utcNow);
			HandleNameListClickProbe(utcNow);
			if (!(utcNow < s_nextRecordUtc))
			{
				s_nextRecordUtc = utcNow.AddSeconds(ValueConversion.Clamp(0.45f, 0.1f, 5f));
				if (!IsMazeAvailable())
				{
					LogMonsterDiagnostics("record-skip:no-maze", "recording skipped because maze data is unavailable; playerTrails=" + s_playerTrails.Count + " monsterTrails=" + s_trails.Count);
					return;
				}
				if (!IsRealMazeRunActive())
				{
					LogMonsterDiagnostics("record-skip:not-real-maze", "recording skipped because active scene is not a real loaded maze; playerTrails=" + s_playerTrails.Count + " monsterTrails=" + s_trails.Count);
					return;
				}
				if (IsCaseOverActive())
				{
					LogMonsterDiagnostics("record-skip:case-over", "recording skipped because case-over screen is active; playerTrails=" + s_playerTrails.Count + " monsterTrails=" + s_trails.Count);
					return;
				}
				float recordingElapsedSeconds = GetRecordingElapsedSeconds(utcNow);
				RecordCurrentPlayers(recordingElapsedSeconds);
				RecordCurrentMonsters(recordingElapsedSeconds);
			}
		}
		catch (Exception ex)
		{
			LogReflectionWarning("recording failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	public override void OnGUI()
	{
		if (s_lastUiInstance == null || (DateTime.UtcNow - s_lastEndMapDrawUtc).TotalSeconds > 30.0 || !EnsureUnityReflection())
		{
			return;
		}
		try
		{
			DrawControlPanel();
		}
		catch (Exception ex)
		{
			if (DateTime.UtcNow >= s_nextGuiWarningUtc)
			{
				s_nextGuiWarningUtc = DateTime.UtcNow.AddSeconds(10.0);
				MelonLogger.Warning("control panel draw failed: " + ex.GetType().Name + " " + ex.Message);
			}
		}
	}

	private static void Increment(Dictionary<string, int> counts, string key)
	{
		if (counts != null)
		{
			key = (string.IsNullOrWhiteSpace(key) ? "unknown" : key);
			counts.TryGetValue(key, out var value);
			counts[key] = value + 1;
		}
	}

	private static void SetCount(Dictionary<string, int> counts, string key, int amount)
	{
		if (counts != null)
		{
			counts[string.IsNullOrWhiteSpace(key) ? "unknown" : key] = amount;
		}
	}

	private static string FormatCounts(Dictionary<string, int> counts)
	{
		if (counts == null || counts.Count == 0)
		{
			return "{}";
		}
		return "{" + string.Join(", ", from p in counts
			orderby p.Key
			select p.Key + "=" + p.Value) + "}";
	}

	private static bool AddPlayersToTexturePrefix(object __instance, object[] __args, [HarmonyArgument(2)] ref float normalizedTime, ref bool __state)
	{
		__state = false;
		try
		{
			if (__instance == null || __args == null || __args.Length < 5)
			{
				return true;
			}
			if (s_internalGamePlayerDraw)
			{
				LogPlayerRenderStatus("native-internal", "native internal call");
				return true;
			}
			float num = (normalizedTime = GetEffectiveNormalizedTime(normalizedTime));
			__args[2] = num;
			s_lastUiInstance = __instance;
			s_lastEndMapDrawUtc = DateTime.UtcNow;
			string source;
			object obj = ResolvePathRecorders(__instance, __args[1], out source);
			object[] array = (object[])__args.Clone();
			array[1] = obj;
			RememberDrawArgs(array, source);
			RefreshPlayerEntries(__instance, obj, source);
			RefreshMonsterEntries();
			SyncGameNameList(__instance, "addplayers-prefix");
			int collectionCount = Il2CppReflection.GetCollectionCount(obj);
			LogEndMapDiagnostics("state:" + collectionCount + ":" + s_playerTrails.Count + ":" + s_trails.Count + ":" + s_showFullPath + ":" + s_paused, "end-map state recorders=" + collectionCount + " recorderSource=" + source + " playerTrails=" + s_playerTrails.Count + " monsterTrails=" + s_trails.Count + " playerEntries=" + s_playerEntries.Count + " monsterEntries=" + s_monsterEntries.Count + " fullPath=" + s_showFullPath + " paused=" + s_paused + " speed=" + s_playbackSpeed.ToString("0.##") + " time=" + ValueConversion.Clamp01(num).ToString("0.00"));
			bool flag = ShouldUseCustomPlayerDraw();
			if (s_paused || flag)
			{
				HideAllDeathIcons(__instance);
			}
			if (!flag)
			{
				LogPlayerRenderStatus("native-custom-disabled", "native renderer because custom player draw is disabled");
				return true;
			}
			bool flag2 = BeginPathTextureBatch(__args[0]);
			if (!flag2 && !ClearPathTexture(__instance, __args[0]))
			{
				LogPlayerRenderStatus("fallback-clear-failed", "native renderer fallback because texture clear failed");
				return true;
			}
			if (!DrawSelectedPlayers(__instance, array, num, !s_showFullPath))
			{
				if (flag2)
				{
					AbortPathTextureBatch();
				}
				LogPlayerRenderStatus("fallback-player-draw-failed", "native renderer fallback because custom player draw failed");
				return true;
			}
			DrawSelectedMonsters(__instance, array, num);
			DrawQueuedPlayerTextureIcons(__instance, array[0]);
			if (flag2)
			{
				if (!FinishPathTextureBatch())
				{
					ClearPathTexture(__instance, __args[0]);
					LogPlayerRenderStatus("fallback-batch-upload-failed", "native renderer fallback because batch texture upload failed");
					return true;
				}
			}
			else
			{
				ApplyTexture(__args[0]);
			}
			__state = true;
			LogPlayerRenderStatus("custom:" + flag2 + ":" + s_showFullPath + ":" + 0, "custom renderer mode=" + (s_showFullPath ? "full path" : "current dots") + " batch=" + flag2 + " extraPixels=" + 0 + " time=" + ValueConversion.Clamp01(num).ToString("0.00"));
			return false;
		}
		catch (Exception ex)
		{
			AbortPathTextureBatch();
			if (DateTime.UtcNow >= s_nextDrawWarningUtc)
			{
				s_nextDrawWarningUtc = DateTime.UtcNow.AddSeconds(10.0);
				MelonLogger.Warning("end-map player draw override failed: " + ex.GetType().Name + " " + ex.Message);
			}
			return true;
		}
	}

	private static bool ClearTextureCoroutineMoveNextPrefix(ref bool __result)
	{
		if (s_allowTextureClearCoroutine)
		{
			return true;
		}
		if (s_lastUiInstance != null && ShouldBlockGameTextureClear() && (DateTime.UtcNow - s_lastEndMapDrawUtc).TotalSeconds < 30.0)
		{
			__result = false;
			return false;
		}
		return true;
	}

	private static void RememberDrawArgs(object[] args, string pathRecordersSource)
	{
		if (args != null && args.Length >= 5)
		{
			s_lastTexture = args[0];
			s_lastPathRecorders = args[1];
			s_lastPathRecordersSource = pathRecordersSource;
			s_lastTilePositionOffset = args[3];
			if (ValueConversion.TryConvertFloat(args[4], out var result))
			{
				s_lastPixelsPerTile = result;
			}
		}
	}

	private static void DriveEndMapPlayback(DateTime now)
	{
		if (s_paused || !HasRecentEndMapContext(now))
		{
			s_lastPlaybackUpdateUtc = DateTime.MinValue;
			return;
		}
		float num = s_scrubTime;
		AdvancePlaybackClock(now);
		if (!(Math.Abs(s_scrubTime - num) < 0.0001f) && !((now - s_lastEndMapDrawUtc).TotalSeconds < 0.03) && !(now < s_nextPlaybackRedrawUtc))
		{
			s_nextPlaybackRedrawUtc = now.AddSeconds(1.0 / 30.0);
			RedrawLastTexture();
		}
	}

	private static bool HasRecentEndMapContext(DateTime now)
	{
		if (s_lastUiInstance != null && s_lastTexture != null && s_lastPathRecorders != null && s_lastTilePositionOffset != null && s_lastPixelsPerTile > 0f)
		{
			return (now - s_lastEndMapDrawUtc).TotalSeconds <= 30.0;
		}
		return false;
	}

	private static void RedrawLastTexture()
	{
		if (s_lastUiInstance == null || s_lastTexture == null || s_lastPathRecorders == null || s_lastTilePositionOffset == null || s_lastPixelsPerTile <= 0f)
		{
			return;
		}
		try
		{
			s_lastEndMapDrawUtc = DateTime.UtcNow;
			object[] args = new object[5]
			{
				s_lastTexture,
				s_lastPathRecorders,
				ValueConversion.Clamp01(s_scrubTime),
				s_lastTilePositionOffset,
				s_lastPixelsPerTile
			};
			SyncGameNameList(s_lastUiInstance, "redraw-last-texture");
			if (ShouldUseCustomPlayerDraw())
			{
				bool flag = BeginPathTextureBatch(s_lastTexture);
				if (!flag && !ClearPathTexture(s_lastUiInstance, s_lastTexture))
				{
					return;
				}
				HideAllDeathIcons(s_lastUiInstance);
				DrawSelectedPlayers(s_lastUiInstance, args, ValueConversion.Clamp01(s_scrubTime), !s_showFullPath);
				DrawSelectedMonsters(s_lastUiInstance, args, ValueConversion.Clamp01(s_scrubTime));
				DrawQueuedPlayerTextureIcons(s_lastUiInstance, s_lastTexture);
				if (flag)
				{
					if (!FinishPathTextureBatch())
					{
						RedrawWithGamePlayerRenderer(args);
					}
				}
				else
				{
					ApplyTexture(s_lastTexture);
				}
			}
			else
			{
				RedrawWithGamePlayerRenderer(args);
			}
		}
		catch
		{
			AbortPathTextureBatch();
		}
	}

	private static void RedrawWithGamePlayerRenderer(object[] args)
	{
		if (ClearPathTexture(s_lastUiInstance, s_lastTexture))
		{
			HideAllDeathIcons(s_lastUiInstance);
			if (!TryInvokeGamePlayerRenderer(s_lastUiInstance, args))
			{
				HideAllDeathIcons(s_lastUiInstance);
				DrawSelectedPlayers(s_lastUiInstance, args, ValueConversion.Clamp01(s_scrubTime), currentOnly: false);
				DrawSelectedMonsters(s_lastUiInstance, args, ValueConversion.Clamp01(s_scrubTime));
				DrawQueuedPlayerTextureIcons(s_lastUiInstance, s_lastTexture);
			}
			ApplyTexture(s_lastTexture);
		}
	}

	private static bool TryInvokeGamePlayerRenderer(object ui, object[] args)
	{
		if (ui == null || args == null || args.Length < 5)
		{
			return false;
		}
		try
		{
			if (s_addPlayersToTextureMethod == null || !s_addPlayersToTextureMethod.DeclaringType.IsInstanceOfType(ui))
			{
				s_addPlayersToTextureMethod = ui.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "AddPlayersToTexture" && m.GetParameters().Length == 5);
			}
			if (s_addPlayersToTextureMethod == null)
			{
				return false;
			}
			s_internalGamePlayerDraw = true;
			try
			{
				s_addPlayersToTextureMethod.Invoke(ui, args);
			}
			finally
			{
				s_internalGamePlayerDraw = false;
			}
			return true;
		}
		catch
		{
			s_internalGamePlayerDraw = false;
			return false;
		}
	}

	private static void AddPlayersToTexturePostfix(object __instance, object[] __args, bool __state, [HarmonyArgument(2)] float normalizedTime)
	{
		try
		{
			if (__instance == null || __args == null || __args.Length < 5 || __state)
			{
				return;
			}
			object obj = __args[0];
			if (obj == null || !ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(__args[3], "x"), out var _) || !ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(__args[3], "y"), out var _) || !ValueConversion.TryConvertFloat(__args[4], out var result3))
			{
				return;
			}
			EnsureDrawReflection(__instance);
			if (!(s_addPlayerDotMethod == null) && !(s_color32Ctor == null) && !(GetMazeTileSize() <= 0f) && !(result3 <= 0f))
			{
				float value = normalizedTime;
				if (ValueConversion.TryConvertFloat(__args[2], out var result4))
				{
					value = result4;
				}
				if (s_paused)
				{
					value = s_scrubTime;
				}
				normalizedTime = ValueConversion.Clamp01(value);
				int trailIndex;
				int num = DrawSelectedMonsters(__instance, __args, normalizedTime, out trailIndex);
				int num2 = DrawQueuedPlayerTextureIcons(__instance, obj);
				if (num > 0 || num2 > 0)
				{
					ApplyTexture(obj);
				}
				if (!s_loggedFirstDraw && IsMonsterChangeLoggingEnabled())
				{
					s_loggedFirstDraw = true;
					MelonLogger.Msg("Drew monster paths on end map: trails=" + trailIndex + " dots=" + num);
				}
			}
		}
		catch (Exception ex)
		{
			if (DateTime.UtcNow >= s_nextDrawWarningUtc)
			{
				s_nextDrawWarningUtc = DateTime.UtcNow.AddSeconds(10.0);
				MelonLogger.Warning("end-map draw failed: " + ex.GetType().Name + " " + ex.Message);
			}
		}
	}

	private static float GetEffectiveNormalizedTime(float normalizedTime)
	{
		if (s_paused)
		{
			s_lastPlaybackUpdateUtc = DateTime.MinValue;
			return ValueConversion.Clamp01(s_scrubTime);
		}
		AdvancePlaybackClock(DateTime.UtcNow);
		return ValueConversion.Clamp01(s_scrubTime);
	}

	private static void AdvancePlaybackClock(DateTime now)
	{
		if (s_lastPlaybackUpdateUtc == DateTime.MinValue)
		{
			s_lastPlaybackUpdateUtc = now;
			return;
		}
		float num = ValueConversion.Clamp((float)(now - s_lastPlaybackUpdateUtc).TotalSeconds, 0f, 0.25f);
		s_lastPlaybackUpdateUtc = now;
		if (num <= 0f)
		{
			return;
		}
		if (s_playbackEndHoldUntilUtc != DateTime.MinValue)
		{
			if (!(now < s_playbackEndHoldUntilUtc))
			{
				s_playbackEndHoldUntilUtc = DateTime.MinValue;
				s_scrubTime = 0f;
			}
			return;
		}
		float num2 = ValueConversion.Clamp(s_playbackSpeed, 0.05f, 4f);
		float num3 = s_scrubTime + num * num2 / 5f;
		if (num3 >= 1f)
		{
			s_scrubTime = 1f;
			s_playbackEndHoldUntilUtc = now.AddSeconds(1.5);
		}
		else
		{
			s_scrubTime = ValueConversion.Clamp01(num3);
		}
	}

	private static bool ShouldUseCustomPlayerDraw()
	{
		return true;
	}

	private static bool ShouldBlockGameTextureClear()
	{
		return true;
	}

	private static object ResolvePathRecorders(object ui, object argPathRecorders, out string source)
	{
		source = "manager-field:pathRecorders";
		string source2;
		object pathVisualisationManagerPathRecorders = GetPathVisualisationManagerPathRecorders(out source2);
		if (pathVisualisationManagerPathRecorders != null && Il2CppReflection.GetCollectionCount(pathVisualisationManagerPathRecorders) > 0)
		{
			source = source2;
			return pathVisualisationManagerPathRecorders;
		}
		source = "arg-wrapper";
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(ui, "pathRecorders");
		int collectionCount = Il2CppReflection.GetCollectionCount(fieldOrProp);
		if (fieldOrProp != null && collectionCount > 0)
		{
			source = "ui-field:pathRecorders";
			return fieldOrProp;
		}
		object fieldOrProp2 = Il2CppReflection.GetFieldOrProp(ui, "PathRecorders");
		int collectionCount2 = Il2CppReflection.GetCollectionCount(fieldOrProp2);
		if (fieldOrProp2 != null && collectionCount2 > 0)
		{
			source = "ui-property:PathRecorders";
			return fieldOrProp2;
		}
		LogPathRecorderResolverDiagnostics(ui, argPathRecorders, fieldOrProp, fieldOrProp2, collectionCount, collectionCount2, pathVisualisationManagerPathRecorders, source2);
		if (argPathRecorders != null)
		{
			return argPathRecorders;
		}
		source = "none";
		return null;
	}

	private static object GetPathVisualisationManagerPathRecorders(out string source)
	{
		source = "manager-field:pathRecorders";
		object staticFieldOrProp = Il2CppReflection.GetStaticFieldOrProp(GetPathVisualisationManagerType(), "Instance");
		if (staticFieldOrProp == null)
		{
			source = "manager:none";
			return null;
		}
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(staticFieldOrProp, "pathRecorders");
		if (fieldOrProp != null && Il2CppReflection.GetCollectionCount(fieldOrProp) > 0)
		{
			source = "manager-field:pathRecorders";
			return fieldOrProp;
		}
		object fieldOrProp2 = Il2CppReflection.GetFieldOrProp(staticFieldOrProp, "PathRecorders");
		if (fieldOrProp2 != null && Il2CppReflection.GetCollectionCount(fieldOrProp2) > 0)
		{
			source = "manager-property:PathRecorders";
			return fieldOrProp2;
		}
		source = ((fieldOrProp != null) ? "manager-field:pathRecorders-empty" : "manager-empty");
		return fieldOrProp ?? fieldOrProp2;
	}

	private static void LogPathRecorderResolverDiagnostics(object ui, object argPathRecorders, object fieldPathRecorders, object propertyPathRecorders, int fieldPathRecorderCount, int propertyPathRecorderCount, object managerPathRecorders, string managerSource)
	{
		if (IsPlayerDebugLoggingEnabled())
		{
			int num = ((ui != null) ? RuntimeHelpers.GetHashCode(ui) : 0);
			LogPlayerTrailDiagnostics("resolver:" + num + ":" + managerSource + ":" + Il2CppReflection.GetCollectionCount(managerPathRecorders) + ":" + Il2CppReflection.GetCollectionCount(argPathRecorders) + ":" + fieldPathRecorderCount + ":" + propertyPathRecorderCount, "path recorder resolver ui=" + DescribeObjectForLog(ui) + " managerSource=" + managerSource + " manager={" + DescribePathRecorderCandidate(managerPathRecorders) + "} arg={" + DescribePathRecorderCandidate(argPathRecorders) + "} field.pathRecorders={" + DescribePathRecorderCandidate(fieldPathRecorders) + "} property.PathRecorders={" + DescribePathRecorderCandidate(propertyPathRecorders) + "} direct={" + DescribeUiPathRecorderDirectAccess(ui) + "}");
		}
	}

	private static string DescribePathRecorderCandidate(object value)
	{
		if (value == null)
		{
			return "null";
		}
		return "type=" + SafeLogToken(value.GetType().FullName ?? value.GetType().Name) + " count=" + Il2CppReflection.GetCollectionCount(value) + " access={" + Il2CppReflection.DescribeCollectionAccessForLog(value, IsPlayerDebugLoggingEnabled()) + "}";
	}

	private static string DescribeUiPathRecorderDirectAccess(object ui)
	{
		if (ui == null)
		{
			return "ui-null";
		}
		try
		{
			Type type = ui.GetType();
			List<string> values = new List<string>
			{
				"type=" + SafeLogToken(type.FullName ?? type.Name),
				DescribeDirectFieldAccess(ui, type, "pathRecorders"),
				DescribeDirectPropertyAccess(ui, type, "PathRecorders"),
				DescribeDirectMethodAccess(ui, type, "get_PathRecorders"),
				"members=[" + FormatPathRecorderMemberHints(type) + "]"
			};
			return string.Join(" ", values);
		}
		catch (Exception ex)
		{
			return "direct-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message);
		}
	}

	private static string DescribeDirectFieldAccess(object ui, Type type, string name)
	{
		try
		{
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null)
			{
				return "field." + name + "=missing";
			}
			try
			{
				return "field." + name + "=" + SafeLogToken(field.FieldType.FullName ?? field.FieldType.Name) + ":" + DescribePathRecorderCandidate(field.GetValue(ui));
			}
			catch (Exception ex)
			{
				return "field." + name + "=" + SafeLogToken(field.FieldType.FullName ?? field.FieldType.Name) + ":get-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message);
			}
		}
		catch (Exception ex2)
		{
			return "field." + name + "=lookup-failed:" + ex2.GetType().Name + ":" + SafeLogToken(ex2.Message);
		}
	}

	private static string DescribeDirectPropertyAccess(object ui, Type type, string name)
	{
		try
		{
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property == null)
			{
				return "property." + name + "=missing";
			}
			try
			{
				return "property." + name + "=" + SafeLogToken(property.PropertyType.FullName ?? property.PropertyType.Name) + ":" + DescribePathRecorderCandidate(property.GetValue(ui));
			}
			catch (Exception ex)
			{
				return "property." + name + "=" + SafeLogToken(property.PropertyType.FullName ?? property.PropertyType.Name) + ":get-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message);
			}
		}
		catch (Exception ex2)
		{
			return "property." + name + "=lookup-failed:" + ex2.GetType().Name + ":" + SafeLogToken(ex2.Message);
		}
	}

	private static string DescribeDirectMethodAccess(object ui, Type type, string name)
	{
		try
		{
			MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == name && m.GetParameters().Length == 0);
			if (methodInfo == null)
			{
				return "method." + name + "=missing";
			}
			try
			{
				return "method." + name + "=" + SafeLogToken(methodInfo.ReturnType.FullName ?? methodInfo.ReturnType.Name) + ":" + DescribePathRecorderCandidate(methodInfo.Invoke(ui, Array.Empty<object>()));
			}
			catch (Exception ex)
			{
				return "method." + name + "=" + SafeLogToken(methodInfo.ReturnType.FullName ?? methodInfo.ReturnType.Name) + ":invoke-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message);
			}
		}
		catch (Exception ex2)
		{
			return "method." + name + "=lookup-failed:" + ex2.GetType().Name + ":" + SafeLogToken(ex2.Message);
		}
	}

	private static string FormatPathRecorderMemberHints(Type type)
	{
		try
		{
			List<string> list = (from m in type.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name.IndexOf("path", StringComparison.OrdinalIgnoreCase) >= 0 || m.Name.IndexOf("record", StringComparison.OrdinalIgnoreCase) >= 0
				select m.MemberType.ToString() + ":" + m.Name).Distinct().Take(30).ToList();
			return (list.Count == 0) ? "none" : string.Join(",", list);
		}
		catch (Exception ex)
		{
			return "member-hints-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message);
		}
	}

	private static void QueuePlayerIcon(List<PlayerIconDrawRequest> requests, string key, WorldPoint point, int offsetX, int offsetY, float pixelsPerTile, float tileSize, object color)
	{
		if (requests != null && !string.IsNullOrEmpty(key))
		{
			int pixelX = ValueConversion.FloorToInt(((float)offsetX + point.X / tileSize + 0.5f) * pixelsPerTile);
			int pixelY = ValueConversion.FloorToInt(((float)offsetY + point.Z / tileSize + 0.5f) * pixelsPerTile);
			requests.Add(new PlayerIconDrawRequest(pixelX, pixelY, key, TrailColorFromColor32(color, new TrailColor(240, 240, 240, byte.MaxValue))));
		}
	}

	private static int DrawQueuedPlayerTextureIcons(object ui, object texture)
	{
		if (s_playerIconDrawRequests.Count == 0 || ui == null || texture == null)
		{
			return 0;
		}
		int width;
		int height;
		bool hasBounds = TryGetTextureDimensions(texture, out width, out height);
		int num = 0;
		int num2 = 0;
		foreach (PlayerIconDrawRequest s_playerIconDrawRequest in s_playerIconDrawRequests)
		{
			if (s_playerIcons.TryGetValue(s_playerIconDrawRequest.Key, out var value) && value != null)
			{
				num += DrawPlayerImageIcon(ui, texture, s_playerIconDrawRequest.PixelX, s_playerIconDrawRequest.PixelY, value, hasBounds, width, height);
				num2++;
			}
			else
			{
				num += DrawGeneratedPlayerIcon(ui, texture, s_playerIconDrawRequest.PixelX, s_playerIconDrawRequest.PixelY, s_playerIconDrawRequest.Color, hasBounds, width, height);
			}
		}
		LogPlayerIconStatus("drawn:" + s_playerIconDrawRequests.Count + ":" + num2 + ":" + num, "player icons drawn=" + num + " requests=" + s_playerIconDrawRequests.Count + " avatars=" + num2 + " cached=" + s_playerIcons.Count);
		return num;
	}

	private static int DrawPlayerImageIcon(object ui, object texture, int centerX, int centerY, MonsterIcon icon, bool hasBounds, int textureWidth, int textureHeight)
	{
		int num = centerX - icon.Width / 2;
		int num2 = centerY - icon.Height / 2;
		return 0 + DrawPixelRect(color: Color32(new TrailColor(0, 0, 0, byte.MaxValue)), ui: ui, texture: texture, x: num - 1, y: num2 - 1, width: icon.Width + 2, height: icon.Height + 2, hasBounds: hasBounds, textureWidth: textureWidth, textureHeight: textureHeight) + DrawMonsterImageIcon(ui, texture, centerX, centerY, icon, hasBounds, textureWidth, textureHeight);
	}

	private static int DrawGeneratedPlayerIcon(object ui, object texture, int centerX, int centerY, TrailColor color, bool hasBounds, int textureWidth, int textureHeight)
	{
		object color2 = Color32(new TrailColor(0, 0, 0, byte.MaxValue));
		object color3 = Color32(OpaqueTrailColor(color));
		object color4 = Color32(new TrailColor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue));
		return 0 + DrawPixelRect(ui, texture, centerX - 5, centerY - 5, 11, 11, color2, hasBounds, textureWidth, textureHeight) + DrawPixelRect(ui, texture, centerX - 4, centerY - 4, 9, 9, color3, hasBounds, textureWidth, textureHeight) + DrawPixelRect(ui, texture, centerX - 2, centerY - 2, 5, 5, color4, hasBounds, textureWidth, textureHeight);
	}

	private static void CachePlayerIcon(string key, object player)
	{
		if (string.IsNullOrEmpty(key))
		{
			LogPlayerIconStatus("cache-empty-key", "player avatar cache skipped: empty key");
		}
		else
		{
			if (s_playerIcons.ContainsKey(key) || (s_playerIconRetryUtc.TryGetValue(key, out var value) && DateTime.UtcNow < value))
			{
				return;
			}
			if (player == null)
			{
				LogPlayerIconStatus("cache-no-player:" + key, "player avatar cache skipped key=" + key + ": player object is null");
				DelayPlayerIconRetry(key);
				return;
			}
			Texture2D playerAvatarTexture = GetPlayerAvatarTexture(player);
			if ((Object)(object)playerAvatarTexture == (Object)null)
			{
				LogPlayerIconStatus("cache-no-avatar:" + key, "player avatar cache skipped key=" + key + ": no avatar texture on " + GetPlayerObjectLabel(player));
				DelayPlayerIconRetry(key);
				return;
			}
			MonsterIcon monsterIcon = CreatePlayerAvatarIcon(key, playerAvatarTexture);
			if (monsterIcon == null)
			{
				LogPlayerIconStatus("cache-null-icon:" + key, "player avatar cache skipped key=" + key + ": avatar texture could not be converted");
				DelayPlayerIconRetry(key);
				return;
			}
			s_playerIconRetryUtc.Remove(key);
			s_playerIcons[key] = monsterIcon;
			LogPlayerIconStatus("cached:" + key, "cached player avatar key=" + key + " size=" + monsterIcon.Width + "x" + monsterIcon.Height + " cached=" + s_playerIcons.Count);
		}
	}

	private static void DelayPlayerIconRetry(string key)
	{
		if (!string.IsNullOrEmpty(key))
		{
			s_playerIconRetryUtc[key] = DateTime.UtcNow.AddSeconds(15.0);
		}
	}

	private static Texture2D GetPlayerAvatarTexture(object player)
	{
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(LabelUtilities.GetPlayerClientData(player), "Avatar");
		Texture2D val = (Texture2D)((fieldOrProp is Texture2D) ? fieldOrProp : null);
		if ((Object)(object)val != (Object)null)
		{
			return val;
		}
		object fieldOrProp2 = Il2CppReflection.GetFieldOrProp(player, "playerAvatar");
		RawImage val2 = (RawImage)((fieldOrProp2 is RawImage) ? fieldOrProp2 : null);
		if (!((Object)(object)val2 == (Object)null))
		{
			Texture texture = val2.texture;
			return (Texture2D)(object)((texture is Texture2D) ? texture : null);
		}
		return null;
	}

	private static MonsterIcon CreatePlayerAvatarIcon(string key, Texture2D source)
	{
		try
		{
			if (TryCreatePlayerAvatarIconFromTexture(source, "player:" + key, 14, out var icon))
			{
				return icon;
			}
			LogPlayerIconStatus("direct-pixels-failed:" + key, "player avatar direct pixel read failed key=" + key + " source={" + DescribeTextureForLog((Texture)(object)source) + "}; trying readable copy");
			Texture2D readable = null;
			try
			{
				if (TryCopyTextureToReadable(source, out readable) && TryCreatePlayerAvatarIconFromTexture(readable, "player:" + key, 14, out icon))
				{
					return icon;
				}
				LogPlayerIconStatus("copy-pixels-failed:" + key, "player avatar readable-copy pixel read failed key=" + key + " source={" + DescribeTextureForLog((Texture)(object)source) + "}");
				return null;
			}
			finally
			{
				if ((Object)(object)readable != (Object)null)
				{
					Object.Destroy((Object)(object)readable);
				}
			}
		}
		catch (Exception ex)
		{
			LogPlayerIconStatus("cache-failed:" + key, "player avatar cache failed key=" + key + ": " + ex.GetType().Name + " " + ex.Message);
			return null;
		}
	}

	private static bool TryCreatePlayerAvatarIconFromTexture(Texture2D source, string key, int iconSize, out MonsterIcon icon)
	{
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: 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)
		icon = null;
		if ((Object)(object)source == (Object)null || iconSize <= 0)
		{
			return false;
		}
		int num = Math.Max(1, ((Texture)source).width);
		int num2 = Math.Max(1, ((Texture)source).height);
		Il2CppStructArray<Color32> pixels = source.GetPixels32();
		if (pixels == null || ((Il2CppArrayBase<Color32>)(object)pixels).Length < num * num2)
		{
			return false;
		}
		TrailColor[] array = new TrailColor[iconSize * iconSize];
		for (int i = 0; i < iconSize; i++)
		{
			int num3 = (iconSize - 1 - i) * Math.Max(0, num2 - 1) / Math.Max(1, iconSize - 1);
			for (int j = 0; j < iconSize; j++)
			{
				int num4 = j * Math.Max(0, num - 1) / Math.Max(1, iconSize - 1);
				Color32 val = ((Il2CppArrayBase<Color32>)(object)pixels)[num3 * num + num4];
				array[i * iconSize + j] = new TrailColor(val.r, val.g, val.b, (byte)((val.a >= 24) ? byte.MaxValue : 0));
			}
		}
		icon = new MonsterIcon(key, iconSize, iconSize, array);
		return true;
	}

	private static int DrawMonsterTextureIcon(object ui, object texture, int centerX, int centerY, int number, TrailColor color, string label)
	{
		int width;
		int height;
		bool hasBounds = TryGetTextureDimensions(texture, out width, out height);
		MonsterIcon monsterIcon = ResolveMonsterIcon(label);
		if (monsterIcon != null)
		{
			return DrawMonsterImageIcon(ui, texture, centerX, centerY, monsterIcon, hasBounds, width, height);
		}
		return DrawGeneratedMonsterIcon(ui, texture, centerX, centerY, number, color, hasBounds, width, height);
	}

	private static int DrawMonsterImageIcon(object ui, object texture, int centerX, int centerY, MonsterIcon icon, bool hasBounds, int textureWidth, int textureHeight)
	{
		int num = 0;
		int num2 = centerX - icon.Width / 2;
		int num3 = centerY - icon.Height / 2;
		object color = Color32(new TrailColor(0, 0, 0, byte.MaxValue));
		num += DrawPixelRect(ui, texture, num2, num3, icon.Width, icon.Height, color, hasBounds, textureWidth, textureHeight);
		for (int i = 0; i < icon.Height; i++)
		{
			for (int j = 0; j < icon.Width; j++)
			{
				TrailColor color2 = icon.Pixels[i * icon.Width + j];
				if (color2.A >= 24 && DrawTexturePixel(ui, texture, num2 + j, num3 + i, Color32(OpaqueTrailColor(color2)), hasBounds, textureWidth, textureHeight))
				{
					num++;
				}
			}
		}
		return num;
	}

	private static int DrawGeneratedMonsterIcon(object ui, object texture, int centerX, int centerY, int number, TrailColor color, bool hasBounds, int textureWidth, int textureHeight)
	{
		object color2 = Color32(new TrailColor(0, 0, 0, byte.MaxValue));
		object color3 = Color32(new TrailColor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue));
		object color4 = Color32(OpaqueTrailColor(color));
		return 0 + DrawPixelRect(ui, texture, centerX - 5, centerY - 5, 11, 11, color2, hasBounds, textureWidth, textureHeight) + DrawPixelRect(ui, texture, centerX - 4, centerY - 4, 9, 9, color4, hasBounds, textureWidth, textureHeight) + DrawMonsterIconNumber(ui, texture, centerX, centerY, number, color3, hasBounds, textureWidth, textureHeight);
	}

	private static int DrawPixelRect(object ui, object texture, int x, int y, int width, int height, object color, bool hasBounds, int textureWidth, int textureHeight)
	{
		int num = 0;
		for (int i = 0; i < height; i++)
		{
			for (int j = 0; j < width; j++)
			{
				if (DrawTexturePixel(ui, texture, x + j, y + i, color, hasBounds, textureWidth, textureHeight))
				{
					num++;
				}
			}
		}
		return num;
	}

	private static int DrawMonsterIconNumber(object ui, object texture, int centerX, int centerY, int number, object color, bool hasBounds, int textureWidth, int textureHeight)
	{
		string text = Math.Max(0, Math.Min(99, number)).ToString();
		int num = text.Length * 3 + Math.Max(0, text.Length - 1);
		int num2 = centerX - num / 2;
		int y = centerY - 2;
		int num3 = 0;
		for (int i = 0; i < text.Length; i++)
		{
			num3 += DrawDigitGlyph(ui, texture, num2 + i * 4, y, text[i], color, hasBounds, textureWidth, textureHeight);
		}
		return num3;
	}

	private static int DrawDigitGlyph(object ui, object texture, int x, int y, char digit, object color, bool hasBounds, int textureWidth, int textureHeight)
	{
		int[] digitGlyph = GetDigitGlyph(digit);
		int num = 0;
		for (int i = 0; i < digitGlyph.Length; i++)
		{
			int num2 = digitGlyph[i];
			for (int j = 0; j < 3; j++)
			{
				if ((num2 & (1 << 2 - j)) != 0 && DrawTexturePixel(ui, texture, x + j, y + i, color, hasBounds, textureWidth, textureHeight))
				{
					num++;
				}
			}
		}
		return num;
	}

	private static bool DrawTexturePixel(object ui, object texture, int x, int y, object color, bool hasBounds, int textureWidth, int textureHeight)
	{
		if (hasBounds && (x < 0 || y < 0 || x >= textureWidth || y >= textureHeight))
		{
			return false;
		}
		if (!TryDrawBatchPixel(texture, x, y, color))
		{
			s_addPlayerDotMethod.Invoke(ui, new object[5] { texture, x, y, color, 1f });
		}
		return true;
	}

	private static int[] GetDigitGlyph(char digit)
	{
		return digit switch
		{
			'0' => new int[5] { 7, 5, 5, 5, 7 }, 
			'1' => new int[5] { 2, 6, 2, 2, 7 }, 
			'2' => new int[5] { 7, 1, 7, 4, 7 }, 
			'3' => new int[5] { 7, 1, 7, 1, 7 }, 
			'4' => new int[5] { 5, 5, 7, 1, 1 }, 
			'5' => new int[5] { 7, 4, 7, 1, 7 }, 
			'6' => new int[5] { 7, 4, 7, 5, 7 }, 
			'7' => new int[5] { 7, 1, 1, 1, 1 }, 
			'8' => new int[5] { 7, 5, 7, 5, 7 }, 
			'9' => new int[5] { 7, 5, 7, 1, 7 }, 
			_ => new int[5], 
		};
	}

	private static MonsterIcon ResolveMonsterIcon(string label)
	{
		EnsureMonsterIconsLoaded();
		if (s_monsterIcons.Count == 0)
		{
			return null;
		}
		string monsterIconKey = GetMonsterIconKey(label);
		if (!string.IsNullOrWhiteSpace(monsterIconKey) && s_monsterIcons.TryGetValue(monsterIconKey, out var value))
		{
			return value;
		}
		if (!s_monsterIcons.TryGetValue("fallback", out var value2))
		{
			return null;
		}
		return value2;
	}

	private static string GetMonsterIconKey(string label)
	{
		string text = NormalizeIconKey(label);
		if (string.IsNullOrWhiteSpace(text))
		{
			return "fallback";
		}
		if (s_monsterIconAliases.TryGetValue(text, out var value))
		{
			return value;
		}
		if (s_monsterIcons.ContainsKey(text))
		{
			return text;
		}
		if (text.Contains("wickerman"))
		{
			return "wickerman";
		}
		if (text.Contains("pigman"))
		{
			return "pigman";
		}
		if (text.Contains("stalker"))
		{
			return "stalker";
		}
		if (text.Contains("zombie"))
		{
			return "zombie";
		}
		if (text.Contains("witch"))
		{
			return "witch";
		}
		if (text.Contains("oni"))
		{
			return "oni";
		}
		if (text.Contains("smiley"))
		{
			return "smiley";
		}
		if (text.Contains("marionette"))
		{
			return "marionette";
		}
		if (text.Contains("clubfoot"))
		{
			return "clubfoot";
		}
		if (text.Contains("partygoer"))
		{
			return "partygoer";
		}
		if (text.Contains("scarecrow"))
		{
			return "scarecrow";
		}
		if (text.Contains("centipede"))
		{
			return "centipede";
		}
		if (text.Contains("archiver"))
		{
			return "archiver";
		}
		if (text.Contains("bride") || text.Contains("lady"))
		{
			return "bridelady";
		}
		return "fallback";
	}

	private static string NormalizeIconKey(string value)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			return string.Empty;
		}
		return new string(value.ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray());
	}

	private static void EnsureMonsterIconsLoaded()
	{
		if (s_monsterIconsLoaded)
		{
			return;
		}
		s_monsterIconsLoaded = true;
		s_monsterIcons.Clear();
		string text = FindMonsterIconFolder();
		if (string.IsNullOrWhiteSpace(text))
		{
			LogMonsterIconStatus("image-icons:no-folder", "image icon folder not found; using generated icons");
			return;
		}
		string[] files = Directory.GetFiles(text, "*.png");
		foreach (string path in files)
		{
			string text2 = NormalizeIconKey(Path.GetFileNameWithoutExtension(path));
			if (!string.IsNullOrWhiteSpace(text2))
			{
				MonsterIcon monsterIcon = LoadMonsterIcon(path, text2);
				if (monsterIcon != null)
				{
					s_monsterIcons[text2] = monsterIcon;
				}
			}
		}
		LogMonsterIconStatus("image-icons:loaded:" + s_monsterIcons.Count, "loaded image icons=" + s_monsterIcons.Count + " folder=" + text);
	}

	private static string FindMonsterIconFolder()
	{
		string path = Path.GetDirectoryName(typeof(MonsterPathVisualisation).Assembly.Location) ?? string.Empty;
		string currentDirectory = Directory.GetCurrentDirectory();
		return new string[4]
		{
			Path.Combine(path, "img"),
			Path.Combine(path, "MonsterPathVisualisation", "img"),
			Path.Combine(currentDirectory, "img"),
			Path.Combine(currentDirectory, "UserData", "MonsterPathVisualisation", "img")
		}.FirstOrDefault((string path2) => Directory.Exists(path2) && Directory.GetFiles(path2, "*.png").Length != 0);
	}

	private static MonsterIcon LoadMonsterIcon(string path, string key)
	{
		try
		{
			if (TryDecodePngIcon(File.ReadAllBytes(path), key, out var icon))
			{
				return icon;
			}
			LogMonsterIconStatus("image-icons:unsupported:" + key, "unsupported PNG icon " + Path.GetFileName(path));
			return null;
		}
		catch (Exception ex)
		{
			LogMonsterIconStatus("image-icons:error:" + key, "icon load failed " + Path.GetFileName(path) + ": " + ex.GetType().Name + " " + ex.Message);
			return null;
		}
	}

	private static bool TryCopyTextureToReadable(Texture2D source, out Texture2D readable)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Expected O, but got Unknown
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		readable = null;
		if ((Object)(object)source == (Object)null)
		{
			return false;
		}
		RenderTexture active = null;
		RenderTexture val = null;
		try
		{
			active = RenderTexture.active;
			val = RenderTexture.GetTemporary(64, 64, 0, (RenderTextureFormat)0);
			Graphics.Blit((Texture)(object)source, val);
			RenderTexture.active = val;
			readable = new Texture2D(64, 64, (TextureFormat)4, false);
			readable.ReadPixels(new Rect(0f, 0f, 64f, 64f), 0, 0, false);
			readable.Apply(false, false);
			return true;
		}
		catch (Exception ex)
		{
			LogPlayerIconStatus("copy-failed:" + DescribeTextureForLog((Texture)(object)source), "player avatar readable copy failed: " + ex.GetType().Name + " " + ex.Message + " source={" + DescribeTextureForLog((Texture)(object)source) + "}");
			if ((Object)(object)readable != (Object)null)
			{
				try
				{
					Object.Destroy((Object)(object)readable);
				}
				catch
				{
				}
			}
			readable = null;
			return false;
		}
		finally
		{
			try
			{
				RenderTexture.active = active;
				if ((Object)(object)val != (Object)null)
				{
					RenderTexture.ReleaseTemporary(val);
				}
			}
			catch
			{
			}
		}
	}

	private static string DescribeTextureForLog(Texture texture)
	{
		if ((Object)(object)texture == (Object)null)
		{
			return "null";
		}
		try
		{
			return "name='" + ((Object)texture).name + "' size=" + texture.width + "x" + texture.height + " type=" + ((object)texture).GetType().Name;
		}
		catch
		{
			return ((object)texture).GetType().Name;
		}
	}

	private static string DescribeObjectForLog(object value)
	{
		if (value == null)
		{
			return "null";
		}
		try
		{
			string text = value.GetType().FullName ?? value.GetType().Name;
			if (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(value, "x"), out var result) && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(value, "y"), out var result2))
			{
				return text + "(" + result.ToString("0.###") + "," + result2.ToString("0.###") + ")";
			}
			return text + " '" + value?.ToString() + "'";
		}
		catch
		{
			return value.GetType().Name;
		}
	}

	private static bool TryDecodePngIcon(byte[] data, string key, out MonsterIcon icon)
	{
		icon = null;
		if (data == null || data.Length < 33)
		{
			return false;
		}
		byte[] array = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };
		for (int i = 0; i < array.Length; i++)
		{
			if (data[i] != array[i])
			{
				return false;
			}
		}
		MemoryStream memoryStream = new MemoryStream();
		byte[] array2 = null;
		byte[] array3 = null;
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		int num5 = 0;
		int num7;
		int num6;
		for (num6 = 8; num6 + 8 <= data.Length; num6 += num7 + 4)
		{
			num7 = ReadBigEndianInt(data, num6);
			num6 += 4;
			if (num7 < 0 || num6 + 4 + num7 + 4 > data.Length)
			{
				return false;
			}
			string text = Encoding.ASCII.GetString(data, num6, 4);
			num6 += 4;
			if (text == "IHDR" && num7 >= 13)
			{
				num = ReadBigEndianInt(data, num6);
				num2 = ReadBigEndianInt(data, num6 + 4);
				num3 = data[num6 + 8];
				num4 = data[num6 + 9];
				num5 = data[num6 + 12];
				continue;
			}
			switch (text)
			{
			case "PLTE":
				array2 = new byte[num7];
				Buffer.BlockCopy(data, num6, array2, 0, num7);
				continue;
			case "tRNS":
				array3 = new byte[num7];
				Buffer.BlockCopy(data, num6, array3, 0, num7);
				continue;
			case "IDAT":
				memoryStream.Write(data, num6, num7);
				continue;
			default:
				continue;
			case "IEND":
				break;
			}
			break;
		}
		if (num <= 0 || num2 <= 0 || num3 != 8 || num5 != 0 || memoryStream.Length <= 6)
		{
			return false;
		}
		int num8 = num4 switch
		{
			0 => 1, 
			2 => 3, 
			3 => 1, 
			4 => 2, 
			6 => 4, 
			_ => 0, 
		};
		if (num8 <= 0 || (num4 == 3 && array2 == null))
		{
			return false;
		}
		byte[] array4 = memoryStream.ToArray();
		byte[] array5;
		try
		{
			using MemoryStream stream = new MemoryStream(array4, 2, array4.Length - 6);
			using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
			using MemoryStream memoryStream2 = new MemoryStream();
			deflateStream.CopyTo(memoryStream2);
			array5 = memoryStream2.ToArray();
		}
		catch
		{
			return false;
		}
		int num9 = num * num8;
		if (array5.Length < (num9 + 1) * num2)
		{
			return false;
		}
		TrailColor[] array6 = new TrailColor[num * num2];
		byte[] array7 = new byte[num9];
		byte[] array8 = new byte[num9];
		int num10 = 0;
		for (int j = 0; j < num2; j++)
		{
			byte filter = array5[num10++];
			Buffer.BlockCopy(array5, num10, array8, 0, num9);
			num10 += num9;
			if (!UnfilterPngScanline(array8, array7, num8, filter))
			{
				return false;
			}
			for (int k = 0; k < num; k++)
			{
				int num11 = k * num8;
				byte b = byte.MaxValue;
				byte r;
				byte g;
				byte b2;
				switch (num4)
				{
				case 0:
					r = (g = (b2 = array8[num11]));
					break;
				case 2:
					r = array8[num11];
					g = array8[num11 + 1];
					b2 = array8[num11 + 2];
					break;
				case 3:
				{
					byte b3 = array8[num11];
					int num12 = b3 * 3;
					if (num12 + 2 >= array2.Length)
					{
						return false;
					}
					r = array2[num12];
					g = array2[num12 + 1];
					b2 = array2[num12 + 2];
					if (array3 != null && b3 < array3.Length)
					{
						b = array3[b3];
					}
					break;
				}
				case 4:
					r = (g = (b2 = array8[num11]));
					b = array8[num11 + 1];
					break;
				case 6:
					r = array8[num11];
					g = array8[num11 + 1];
					b2 = array8[num11 + 2];
					b = array8[num11 + 3];
					break;
				default:
					return false;
				}
				array6[j * num + k] = new TrailColor(r, g, b2, (byte)((b >= 24) ? byte.MaxValue : 0));
			}
			byte[] array9 = array7;
			array7 = array8;
			array8 = array9;
		}
		icon = new MonsterIcon(key, num, num2, array6);
		return true;
	}

	private static int ReadBigEndianInt(byte[] data, int offset)
	{
		return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
	}

	private static bool UnfilterPngScanline(byte[] current, byte[] previous, int bytesPerPixel, int filter)
	{
		switch (filter)
		{
		case 0:
			return true;
		case 1:
		{
			for (int l = 0; l < current.Length; l++)
			{
				int num2 = ((l >= bytesPerPixel) ? current[l - bytesPerPixel] : 0);
				current[l] = (byte)(current[l] + num2);
			}
			return true;
		}
		case 2:
		{
			for (int j = 0; j < current.Length; j++)
			{
				current[j] += previous[j];
			}
			return true;
		}
		case 3:
		{
			for (int k = 0; k < current.Length; k++)
			{
				int num = ((k >= bytesPerPixel) ? current[k - bytesPerPixel] : 0);
				byte b = previous[k];
				current[k] = (byte)(current[k] + (num + b >> 1));
			}
			return true;
		}
		case 4:
		{
			for (int i = 0; i < current.Length; i++)
			{
				int left = ((i >= bytesPerPixel) ? current[i - bytesPerPixel] : 0);
				byte up = previous[i];
				int upperLeft = ((i >= bytesPerPixel) ? previous[i - bytesPerPixel] : 0);
				current[i] = (byte)(current[i] + PaethPredictor(left, up, upperLeft));
			}
			return true;
		}
		default:
			return false;
		}
	}

	private static int PaethPredictor(int left, int up, int upperLeft)
	{
		int num = left + up - upperLeft;
		int num2 = Math.Abs(num - left);
		int num3 = Math.Abs(num - up);
		int num4 = Math.Abs(num - upperLeft);
		if (num2 <= num3 && num2 <= num4)
		{
			return left;
		}
		if (num3 > num4)
		{
			return upperLeft;
		}
		return up;
	}

	private static bool IsCaseOverActive()
	{
		try
		{
			object staticFieldOrProp = Il2CppReflection.GetStaticFieldOrProp(GetEndScreenUIType(), "Instance");
			if (staticFieldOrProp == null)
			{
				return false;
			}
			bool result;
			return ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(staticFieldOrProp, "IsVisible"), out result) && result;
		}
		catch
		{
			return false;
		}
	}

	private static bool IsPlayerSyncCandidate(object obj)
	{
		if (obj == null || LooksLikeMonsterObject(obj))
		{
			return false;
		}
		Type playerNetworkSyncType = GetPlayerNetworkSyncType();
		if (playerNetworkSyncType != null && playerNetworkSyncType.IsInstanceOfType(obj))
		{
			return true;
		}
		string text = obj.GetType().FullName ?? obj.GetType().Name;
		if (text.EndsWith("PlayerNetworkSync", StringComparison.Ordinal) || text.EndsWith(".PlayerNetworkSync", StringComparison.Ordinal))
		{
			return true;
		}
		if (Il2CppReflection.GetFieldOrProp(obj, "ClientData") == null && Il2CppReflection.GetFieldOrProp(obj, "isDead") == null)
		{
			return Il2CppReflection.GetFieldOrProp(obj, "NetworkisDead") != null;
		}
		return true;
	}

	private static bool LooksLikeMonsterObject(object obj)
	{
		if (obj == null)
		{
			return false;
		}
		if (TryGetKnownMonsterObjectLabel(obj, out var _))
		{
			return !HasMonsterLikeAncestor(obj);
		}
		return false;
	}

	private static bool TryGetPlayerDeadState(object player, out bool isDead)
	{
		isDead = false;
		if (!ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(player, "IsDead"), out isDead) && !ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(player, "NetworkisDead"), out isDead))
		{
			return ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(player, "isDead"), out isDead);
		}
		return true;
	}

	private static object GetMonsterNetworkSyncObject(object monster)
	{
		if (monster == null)
		{
			return null;
		}
		Type monsterNetworkSyncType = GetMonsterNetworkSyncType();
		if (monsterNetworkSyncType != null && monsterNetworkSyncType.IsInstanceOfType(monster))
		{
			return monster;
		}
		return Il2CppReflection.GetFieldOrProp(monster, "MonsterNetworkSync") ?? Il2CppReflection.GetFieldOrProp(monster, "_MonsterNetworkSync_k__BackingField");
	}

	private static object GetMonsterControllerObject(object monster)
	{
		if (monster == null)
		{
			return null;
		}
		Type aIControllerType = GetAIControllerType();
		if (aIControllerType != null && aIControllerType.IsInstanceOfType(monster))
		{
			return monster;
		}
		object monsterNetworkSyncObject = GetMonsterNetworkSyncObject(monster);
		return Il2CppReflection.GetFieldOrProp(monster, "AIController") ?? Il2CppReflection.GetFieldOrProp(monster, "_AIController_k__BackingField") ?? Il2CppReflection.GetFieldOrProp(monsterNetworkSyncObject, "AIController") ?? Il2CppReflection.GetFieldOrProp(monsterNetworkSyncObject, "_AIController_k__BackingField");
	}

	private static string GetMonsterKey(object monster)
	{
		return "object:" + RuntimeHelpers.GetHashCode(monster);
	}

	private static string GetPlayerRecorderTrailKey(object recorder, int index)
	{
		return GetPlayerObjectKey(Il2CppReflection.GetFieldOrProp(recorder, "player")) ?? ("player-index:" + index);
	}

	private static string GetLocalPlayerKey()
	{
		string playerObjectKey = GetPlayerObjectKey(Il2CppReflection.GetStaticFieldOrProp(GetPlayerNetworkSyncType(), "instance"));
		if (!string.IsNullOrEmpty(playerObjectKey))
		{
			return playerObjectKey;
		}
		return GetPlayerObjectKey(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetStaticFieldOrProp(GetGameManagerType(), "instance"), "mainPlayer"));
	}

	private static string GetPlayerObjectKey(object player)
	{
		if (player == null)
		{
			return null;
		}
		if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "netId"), out var result) && result > 0)
		{
			return "net:" + result;
		}
		object playerClientData = LabelUtilities.GetPlayerClientData(player);
		if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(playerClientData, "SteamID") ?? Il2CppReflection.GetFieldOrProp(playerClientData, "SteamId") ?? Il2CppReflection.GetFieldOrProp(playerClientData, "steamID") ?? Il2CppReflection.GetFieldOrProp(playerClientData, "ID"), out var result2) && result2 > 0)
		{
			return "steam:" + result2;
		}
		if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "ID") ?? Il2CppReflection.GetFieldOrProp(player, "PlayerID") ?? Il2CppReflection.GetFieldOrProp(player, "playerID") ?? Il2CppReflection.GetFieldOrProp(player, "ActorNumber"), out var result3) && result3 > 0)
		{
			return "player:" + result3;
		}
		return "object:" + RuntimeHelpers.GetHashCode(player);
	}

	private static string GetMonsterLabel(object monster)
	{
		if (TryGetKnownMonsterObjectLabel(monster, out var label))
		{
			return label;
		}
		object obj = GetMonsterControllerObject(monster) ?? monster;
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(obj, "MonsterType");
		if (fieldOrProp == null)
		{
			fieldOrProp = Il2CppReflection.GetFieldOrProp(obj, "monsterType");
		}
		if (fieldOrProp != null)
		{
			return CanonicalMonsterLabel(fieldOrProp.ToString(), "Monster");
		}
		return CanonicalMonsterLabel(LabelUtilities.GetObjectName(monster), "Monster");
	}

	private static string CanonicalMonsterLabel(string label, string fallback)
	{
		if (string.IsNullOrWhiteSpace(label))
		{
			return fallback;
		}
		string text = NormalizeMonsterObjectName(label);
		if (text.Length > 0 && KnownMonsterObjectNameMap().TryGetValue(text, out var value) && !string.IsNullOrWhiteSpace(value))
		{
			return value;
		}
		return LabelUtilities.CleanObjectLabel(label);
	}

	private static string GetPlayerObjectLabel(object player)
	{
		string source;
		return GetPlayerObjectLabel(player, out source);
	}

	private static string GetPlayerObjectLabel(object player, out string source)
	{
		return ResolvePlayerLabel(player, null, -1, out source);
	}

	private static string GetPlayerLabel(object ui, object recorder, int index)
	{
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(recorder, "player");
		string source;
		string text = ResolvePlayerLabel(fieldOrProp, ui, index, out source);
		if (source.StartsWith("fallback", StringComparison.OrdinalIgnoreCase))
		{
			LogPlayerNameDiagnostics("fallback:" + index + ":" + LabelUtilities.DescribePlayerObject(fieldOrProp), "index=" + index + " source=" + source + " label='" + text + "' details=" + LabelUtilities.DescribePlayerNameSources(fieldOrProp, ui, index));
		}
		return text;
	}

	private static string ResolvePlayerLabel(object player, object ui, int index, out string source)
	{
		string clientDataName = LabelUtilities.GetClientDataName(LabelUtilities.GetPlayerClientData(player, out var source2), out var source3);
		if (LabelUtilities.IsUsefulPlayerLabel(clientDataName))
		{
			source = source2 + "." + source3;
			return clientDataName;
		}
		clientDataName = LabelUtilities.GetTextValue(Il2CppReflection.GetFieldOrProp(player, "namePlate"));
		if (LabelUtilities.IsUsefulPlayerLabel(clientDataName))
		{
			source = "namePlate.text";
			return clientDataName;
		}
		clientDataName = LabelUtilities.GetEndMapPlayerName(ui, index);
		if (LabelUtilities.IsUsefulPlayerLabel(clientDataName))
		{
			source = "endMap.playerNameUIs";
			return clientDataName;
		}
		clientDataName = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(player, "gameObject") ?? player, "name") as string;
		if (LabelUtilities.IsUsefulPlayerLabel(clientDataName))
		{
			source = "gameObject.name";
			return clientDataName;
		}
		source = ((index >= 0) ? "fallback:index" : "fallback:generic");
		if (index < 0)
		{
			return "Player";
		}
		return "Player " + (index + 1);
	}

	private static void LogReflectionWarning(string message)
	{
		if (!(DateTime.UtcNow < s_nextReflectionWarningUtc))
		{
			s_nextReflectionWarningUtc = DateTime.UtcNow.AddSeconds(10.0);
			MelonLogger.Warning(message);
		}
	}

	private static void LogPlayerRenderStatus(string signature, string message)
	{
		if (IsVerboseLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!(signature == s_lastPlayerRenderStatus) || !(utcNow < s_nextPlayerRenderStatusUtc))
			{
				s_lastPlayerRenderStatus = signature;
				s_nextPlayerRenderStatusUtc = utcNow.AddSeconds(4.0);
				MelonLogger.Msg("Player path renderer: " + message);
			}
		}
	}

	private static void LogMonsterIconStatus(string signature, string message)
	{
		if (IsVerboseLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!(utcNow < s_nextMonsterIconDebugUtc))
			{
				s_nextMonsterIconDebugUtc = utcNow.AddSeconds(3.0);
				MelonLogger.Msg("Monster icon debug: " + message);
			}
		}
	}

	private static void LogPlayerIconStatus(string signature, string message)
	{
		if (IsVerboseLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!(utcNow < s_nextPlayerIconDebugUtc))
			{
				s_nextPlayerIconDebugUtc = utcNow.AddSeconds(3.0);
				MelonLogger.Msg("Player icon debug: " + message);
			}
		}
	}

	private static void LogVerbose(string message)
	{
		if (IsVerboseLoggingEnabled())
		{
			MelonLogger.Msg(message);
		}
	}

	private static bool IsVerboseLoggingEnabled()
	{
		if (s_verboseLogs != null)
		{
			return s_verboseLogs.Value;
		}
		return false;
	}

	private static bool IsMonsterChangeLoggingEnabled()
	{
		return IsVerboseLoggingEnabled();
	}

	private static bool IsPlayerDebugLoggingEnabled()
	{
		if (s_debugPlayerPath == null || !s_debugPlayerPath.Value)
		{
			return IsVerboseLoggingEnabled();
		}
		return true;
	}

	private static void LogPlayerDebug(string message)
	{
		if (IsPlayerDebugLoggingEnabled() && !(DateTime.UtcNow < s_nextPlayerDebugLogUtc))
		{
			s_nextPlayerDebugLogUtc = DateTime.UtcNow.AddSeconds(3.0);
			MelonLogger.Msg("Player path debug: " + message);
		}
	}

	private static void LogPlayerTrailDiagnostics(string signature, string message)
	{
		if (IsPlayerDebugLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!s_nextPlayerTrailDiagnosticsBySignature.TryGetValue(signature, out var value) || !(utcNow < value))
			{
				s_nextPlayerTrailDiagnosticsBySignature[signature] = utcNow.AddSeconds(5.0);
				MelonLogger.Msg("Player trail diagnostics: " + message);
			}
		}
	}

	private static void AddPlayerRecorderDebug(List<string> rows, int index, string label, string recordedKey, string localKey, string matchedKey, MonsterTrail matchedTrail, int rawPositionCount, string decision)
	{
		if (rows != null && rows.Count < 8)
		{
			bool flag = !string.IsNullOrEmpty(recordedKey) && !string.IsNullOrEmpty(localKey) && string.Equals(recordedKey, localKey, StringComparison.OrdinalIgnoreCase);
			rows.Add("#" + index + " label='" + SafeLogToken(label) + "' key=" + (string.IsNullOrEmpty(recordedKey) ? "{none}" : recordedKey) + " local=" + flag + " raw=" + rawPositionCount + " match=" + (string.IsNullOrEmpty(matchedKey) ? "{none}" : matchedKey) + "#" + (matchedTrail?.Count ?? 0) + " decision=" + decision);
		}
	}

	private static string FormatPlayerRecorderDebugRows(List<string> rows)
	{
		if (rows == null || rows.Count == 0)
		{
			return string.Empty;
		}
		return " recorderDetails=[" + string.Join(" | ", rows) + "]";
	}

	private static string FormatPathEntryKeysForLog(IReadOnlyList<PathEntry> entries)
	{
		if (entries == null || entries.Count == 0)
		{
			return "none";
		}
		List<string> list = (from entry in entries.Take(12)
			select entry.Key ?? "{none}").ToList();
		if (entries.Count > 12)
		{
			list.Add("+" + (entries.Count - 12));
		}
		return string.Join(",", list);
	}

	private static string FormatPathEntriesForLog(IReadOnlyList<PathEntry> entries)
	{
		if (entries == null || entries.Count == 0)
		{
			return "[]";
		}
		List<string> list = (from entry in entries.Take(12)
			select (entry.Key ?? "{none}") + "='" + SafeLogToken(entry.Label) + "'").ToList();
		if (entries.Count > 12)
		{
			list.Add("+" + (entries.Count - 12) + " more");
		}
		return "[" + string.Join("; ", list) + "]";
	}

	private static string SafeLogToken(string value)
	{
		if (!string.IsNullOrWhiteSpace(value))
		{
			return value.Replace("|", "/").Replace("\r", " ").Replace("\n", " ")
				.Trim();
		}
		return string.Empty;
	}

	private static void LogMonsterDiagnostics(string signature, string message)
	{
		if (IsMonsterChangeLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!(signature == s_lastMonsterDiagnosticsStatus) || !(utcNow < s_nextMonsterDiagnosticsUtc))
			{
				s_lastMonsterDiagnosticsStatus = signature;
				s_nextMonsterDiagnosticsUtc = utcNow.AddSeconds(8.0);
				MelonLogger.Msg("Monster path diagnostics: " + message);
			}
		}
	}

	private static void LogPlayerNameDiagnostics(string signature, string message)
	{
		if (IsPlayerDebugLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!(signature == s_lastPlayerNameDiagnosticsStatus) || !(utcNow < s_nextPlayerNameDiagnosticsUtc))
			{
				s_lastPlayerNameDiagnosticsStatus = signature;
				s_nextPlayerNameDiagnosticsUtc = utcNow.AddSeconds(8.0);
				MelonLogger.Msg("Player name diagnostics: " + message);
			}
		}
	}

	private static void LogEndMapDiagnostics(string signature, string message)
	{
		if (IsVerboseLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!(signature == s_lastEndMapDiagnosticsStatus) || !(utcNow < s_nextEndMapDiagnosticsUtc))
			{
				s_lastEndMapDiagnosticsStatus = signature;
				s_nextEndMapDiagnosticsUtc = utcNow.AddSeconds(6.0);
				MelonLogger.Msg("End-map path diagnostics: " + message);
			}
		}
	}

	private static void LogNameListDiagnostics(string signature, string message)
	{
		if (IsVerboseLoggingEnabled())
		{
			DateTime utcNow = DateTime.UtcNow;
			if (!s_nextNameListDiagnosticsBySignature.TryGetValue(signature, out var value) || !(utcNow < value))
			{
				s_nextNameListDiagnosticsBySignature[signature] = utcNow.AddSeconds(6.0);
				MelonLogger.Msg("Name-list diagnostics: " + message);
			}
		}
	}

	private static void DrawNameList(string source)
	{
		List<PathEntry> nameListEntries = GetNameListEntries();
		if (nameListEntries.Count > 0)
		{
			SyncGameNameList(s_lastUiInstance, nameListEntries, source);
		}
	}

	private static List<PathEntry> GetNameListEntries()
	{
		s_nameListEntries.Clear();
		s_nameListEntries.AddRange(s_playerEntries);
		s_nameListEntries.AddRange(s_monsterEntries);
		return s_nameListEntries;
	}

	private static void HandleNameListClickProbe(DateTime now)
	{
		if (s_lastUiInstance == null || (now - s_lastEndMapDrawUtc).TotalSeconds > 30.0 || !TryGetMouseButtonDown(out var x, out var y, out var source))
		{
			return;
		}
		List<PathEntry> nameListEntries = GetNameListEntries();
		if (nameListEntries.Count <= 0)
		{
			return;
		}
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(s_lastUiInstance, "playerNameUIs");
		int num = Math.Min(nameListEntries.Count, Il2CppReflection.GetCollectionCount(fieldOrProp) + GetExtraNameListRowCount(s_lastUiInstance));
		if (num <= 0)
		{
			return;
		}
		int count = nameListEntries.Count;
		int columnCount = Math.Max(1, (int)Math.Ceiling((float)count / 5f));
		float uiScale = NameListScale(Math.Min(5, count), columnCount);
		int num2 = Math.Min(nameListEntries.Count, num);
		float num3 = x;
		float num4 = (float)ScreenHeight() - y;
		int num5 = 0;
		int num6 = 0;
		int num7 = -1;
		List<string> rows = (IsVerboseLoggingEnabled() ? new List<string>() : null);
		for (int i = 0; i < num2; i++)
		{
			if (TryGetGameNameListClickTargetRect(i, uiScale, out var x2, out var y2, out var width, out var height, out var source2))
			{
				num5++;
				bool flag = num3 >= x2 && num3 <= x2 + width && num4 >= y2 && num4 <= y2 + height;
				AddNameListClickDiagnostic(rows, i, nameListEntries[i].Label, x2, y2, width, height, flag, source2);
				if (flag && num7 < 0)
				{
					num7 = i;
				}
			}
			else
			{
				num6++;
			}
		}
		if (num7 >= 0)
		{
			PathEntry pathEntry = nameListEntries[num7];
			s_pathVisibility[pathEntry.Key] = !IsPathVisible(pathEntry.Key);
			RedrawLastTexture();
		}
		if (IsVerboseLoggingEnabled())
		{
			MelonLogger.Msg("[NameListClickProbe] mouse=" + (int)num3 + "," + (int)num4 + " raw=" + (int)x + "," + (int)y + " input=" + source + " entries=" + nameListEntries.Count + " synced=" + num + " candidates=" + num5 + " missingRects=" + num6 + " hit=" + ((num7 >= 0) ? (num7 + " '" + SafeLogToken(nameListEntries[num7].Label) + "'") : "none") + FormatNameListClickDiagnostics(rows));
		}
	}

	private static bool TryGetMouseButtonDown(out float x, out float y, out string source)
	{
		x = 0f;
		y = 0f;
		source = "none";
		if (!EnsureUnityReflection())
		{
			source = "unity-reflection-not-ready";
			return false;
		}
		if (s_inputType == null || s_inputGetMouseButtonDown == null || s_inputMousePositionProperty == null)
		{
			source = "input-reflection-missing";
			LogNameListDiagnostics("click-input-missing", "click probe input unavailable inputType=" + ((s_inputType == null) ? "none" : s_inputType.FullName) + " getMouseButtonDown=" + ((s_inputGetMouseButtonDown == null) ? "none" : "ok") + " mousePosition=" + ((s_inputMousePositionProperty == null) ? "none" : "ok"));
			return false;
		}
		try
		{
			if (!Convert.ToBoolean(s_inputGetMouseButtonDown.Invoke(null, new object[1] { 0 })))
			{
				return false;
			}
			object value = s_inputMousePositionProperty.GetValue(null);
			if (!ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(value, "x"), out x) || !ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(value, "y"), out y))
			{
				source = "mouse-position-unreadable";
				LogNameListDiagnostics("click-input-mouse-position-unreadable:" + ((value == null) ? "none" : value.GetType().FullName), "[NameListClickProbe] input=mouse-position-unreadable type=" + ((value == null) ? "none" : value.GetType().FullName));
				return false;
			}
			source = "UnityEngine.Input(reflection)";
			return true;
		}
		catch (Exception ex)
		{
			source = "input-exception";
			LogNameListDiagnostics("click-input-exception:" + ex.GetType().Name, "click probe input failed: " + ex.GetType().Name + " " + ex.Message);
			return false;
		}
	}

	private static bool TryGetGameNameListClickTargetRect(int index, float uiScale, out float x, out float y, out float width, out float height, out string source)
	{
		source = "none";
		float x2;
		float y2;
		float width2;
		float height2;
		bool flag = TryGetGameNameListElementGuiRect(index, out x2, out y2, out width2, out height2);
		if (TryGetGameNameListTextGuiRect(index, out var x3, out var y3, out var width3, out var height3))
		{
			float num = Math.Max(height3 + S(8f, uiScale), S(22f, uiScale));
			x = Math.Max(0f, x3 - num);
			y = y3;
			width = width3 + num;
			height = height3;
			if (flag && width2 > 1f && height2 > 1f)
			{
				UnionGuiRects(ref x, ref y, ref width, ref height, x2, y2, Math.Min(width2, S(260f, uiScale)), height2);
				ExpandGuiRect(ref x, ref y, ref width, ref height, S(4f, uiScale), S(3f, uiScale));
				source = "element-text-union";
				if (width > 1f)
				{
					return height > 1f;
				}
				return false;
			}
			source = "text-expanded-left";
			if (width > 1f)
			{
				return height > 1f;
			}
			return false;
		}
		if (flag)
		{
			x = x2;
			y = y2;
			width = Math.Min(width2, S(260f, uiScale));
			height = height2;
			ExpandGuiRect(ref x, ref y, ref width, ref height, S(4f, uiScale), S(3f, uiScale));
			source = "element";
			if (width > 1f)
			{
				return height > 1f;
			}
			return false;
		}
		x = 0f;
		y = 0f;
		width = 0f;
		height = 0f;
		return false;
	}

	private static void UnionGuiRects(ref float x, ref float y, ref float width, ref float height, float otherX, float otherY, float otherWidth, float otherHeight)
	{
		float num = Math.Min(x, otherX);
		float num2 = Math.Min(y, otherY);
		float num3 = Math.Max(x + width, otherX + otherWidth);
		float num4 = Math.Max(y + height, otherY + otherHeight);
		x = num;
		y = num2;
		width = Math.Max(0f, num3 - num);
		height = Math.Max(0f, num4 - num2);
	}

	private static void ExpandGuiRect(ref float x, ref float y, ref float width, ref float height, float horizontal, float vertical)
	{
		x = Math.Max(0f, x - Math.Max(0f, horizontal));
		y = Math.Max(0f, y - Math.Max(0f, vertical));
		width += Math.Max(0f, horizontal) * 2f;
		height += Math.Max(0f, vertical) * 2f;
	}

	private static bool TryGetGameNameListElementGuiRect(int index, out float x, out float y, out float width, out float height)
	{
		x = 0f;
		y = 0f;
		width = 0f;
		height = 0f;
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(s_lastUiInstance, "playerNameUIs");
		int collectionCount = Il2CppReflection.GetCollectionCount(fieldOrProp);
		string source;
		if (index >= collectionCount)
		{
			ExtraNameListRow extraNameListRow = GetExtraNameListRow(index - collectionCount);
			if (extraNameListRow != null)
			{
				return TryGetObjectGuiRect(extraNameListRow.Element, out x, out y, out width, out height, out source);
			}
			return false;
		}
		if (!Il2CppReflection.TryGetIndexedItem(fieldOrProp, index, out var item) || item == null)
		{
			return false;
		}
		return TryGetObjectGuiRect(Il2CppReflection.GetFieldOrProp(item, "element"), out x, out y, out width, out height, out source);
	}

	private static void LogNameListSecondaryPanelDiagnostics(GameObject panel, RectTransform rectTransform, int entryCount, int extraRowLevels, float extension, strin