Decompiled source of MonsterPathVisualisation v0.4.1

MonsterPathVisualisation.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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 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.1", "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 = 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 = 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 = 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 = 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 = 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 = 1;

	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 List<object> s_cachedGameObjectFallbackMonsters = 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 object s_lastTilePositionOffset;

	private static float s_lastPixelsPerTile;

	private static bool s_inRealMazeScene;

	private static string s_currentRealMazeSceneName;

	private static DateTime s_nextGameObjectFallbackScanUtc = DateTime.MinValue;

	private static int s_fastGameObjectFallbackScansRemaining;

	private static string s_lastFallbackObjectDumpSignature;

	private static DateTime s_nextFallbackObjectDumpUtc = DateTime.MinValue;

	private static Type s_pathVisualisationUiType;

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

	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;

	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(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 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;
			RememberDrawArgs(__args);
			RefreshPlayerEntries(__instance, __args[1]);
			RefreshMonsterEntries();
			SyncGameNameList(__instance, "addplayers-prefix");
			LogEndMapDiagnostics("state:" + GetCollectionCount(__args[1]) + ":" + s_playerTrails.Count + ":" + s_trails.Count + ":" + s_showFullPath + ":" + s_paused, "end-map state recorders=" + GetCollectionCount(__args[1]) + " 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=" + 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, __args, num, !s_showFullPath))
			{
				if (flag2)
				{
					AbortPathTextureBatch();
				}
				LogPlayerRenderStatus("fallback-player-draw-failed", "native renderer fallback because custom player draw failed");
				return true;
			}
			DrawSelectedMonsters(__instance, __args, num);
			DrawQueuedPlayerTextureIcons(__instance, __args[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=" + 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)
	{
		if (args != null && args.Length >= 5)
		{
			s_lastTexture = args[0];
			s_lastPathRecorders = args[1];
			s_lastTilePositionOffset = args[3];
			if (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,
				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, Clamp01(s_scrubTime), !s_showFullPath);
				DrawSelectedMonsters(s_lastUiInstance, args, 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, Clamp01(s_scrubTime), currentOnly: false);
				DrawSelectedMonsters(s_lastUiInstance, args, 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 || !TryConvertInt(GetFieldOrProp(__args[3], "x"), out var _) || !TryConvertInt(GetFieldOrProp(__args[3], "y"), out var _) || !TryConvertFloat(__args[4], out var result3))
			{
				return;
			}
			EnsureDrawReflection(__instance);
			if (!(s_addPlayerDotMethod == null) && !(s_color32Ctor == null) && !(GetMazeTileSize() <= 0f) && !(result3 <= 0f))
			{
				float value = normalizedTime;
				if (TryConvertFloat(__args[2], out var result4))
				{
					value = result4;
				}
				if (s_paused)
				{
					value = s_scrubTime;
				}
				normalizedTime = 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 Clamp01(s_scrubTime);
		}
		AdvancePlaybackClock(DateTime.UtcNow);
		return Clamp01(s_scrubTime);
	}

	private static void AdvancePlaybackClock(DateTime now)
	{
		if (s_lastPlaybackUpdateUtc == DateTime.MinValue)
		{
			s_lastPlaybackUpdateUtc = now;
			return;
		}
		float num = 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 = 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 = Clamp01(num3);
		}
	}

	private static bool ShouldUseCustomPlayerDraw()
	{
		return true;
	}

	private static bool ShouldBlockGameTextureClear()
	{
		return true;
	}

	private static void RefreshPlayerEntries(object ui, object pathRecorders)
	{
		s_playerEntries.Clear();
		object fieldOrProp = GetFieldOrProp(ui, "playerColors");
		int collectionCount = GetCollectionCount(pathRecorders);
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		int num = collectionCount;
		foreach (KeyValuePair<string, MonsterTrail> item2 in from p in s_playerTrails
			orderby p.Value.Count descending, p.Key
			select p)
		{
			if (!hashSet.Contains(item2.Key) && item2.Value.Count > 0 && !ShouldSkipUnmatchedPlayerTrail(item2.Key, item2.Value, collectionCount, hashSet2))
			{
				TrailColor trailColor = new TrailColor(240, 230, 58, byte.MaxValue);
				if (TryGetIndexedItem(fieldOrProp, num, out var item))
				{
					trailColor = TrailColorFromColor32(item, trailColor);
				}
				string key = PlayerTrailVisibilityKey(item2.Key);
				EnsureVisibilityKey(key);
				string label = (string.IsNullOrWhiteSpace(item2.Value.Label) ? ("Player " + (num + 1)) : item2.Value.Label);
				s_playerEntries.Add(new PathEntry(key, label, trailColor));
				AddNormalizedPlayerLabel(hashSet2, label);
				num++;
			}
		}
	}

	private static void RefreshMonsterEntries()
	{
		s_monsterEntries.Clear();
		int num = 1;
		foreach (KeyValuePair<string, MonsterTrail> item in s_trails.OrderBy((KeyValuePair<string, MonsterTrail> p) => p.Value.ColorIndex))
		{
			MonsterTrail value = item.Value;
			TrailColor color = s_palette[value.ColorIndex % s_palette.Length];
			string label = (string.IsNullOrWhiteSpace(value.Label) ? ("Monster " + num) : (value.Label + " " + num));
			string key = MonsterVisibilityKey(item.Key);
			EnsureVisibilityKey(key);
			s_monsterEntries.Add(new PathEntry(key, label, color));
			num++;
		}
	}

	private static string MonsterVisibilityKey(string trailKey)
	{
		return "monster:" + trailKey;
	}

	private static string PlayerTrailVisibilityKey(string trailKey)
	{
		return "recorded-player:" + trailKey;
	}

	private static bool TryFindMatchingPlayerTrailForRecorder(object ui, object recorder, int index, string recorderLabel, HashSet<string> usedKeys, out string matchedKey, out MonsterTrail trail)
	{
		matchedKey = null;
		trail = null;
		string playerRecorderTrailKey = GetPlayerRecorderTrailKey(recorder, index);
		if (!string.IsNullOrEmpty(playerRecorderTrailKey) && (usedKeys == null || !usedKeys.Contains(playerRecorderTrailKey)) && s_playerTrails.TryGetValue(playerRecorderTrailKey, out trail) && trail != null && trail.Count > 0)
		{
			matchedKey = playerRecorderTrailKey;
			return true;
		}
		return false;
	}

	private static bool ShouldSkipUnmatchedPlayerTrail(string key, MonsterTrail trail, int recorderCount, HashSet<string> matchedLabels)
	{
		if (recorderCount > 0 && string.Equals(key, "local-camera", StringComparison.OrdinalIgnoreCase))
		{
			return true;
		}
		string text = NormalizePlayerLabelForMatch(trail?.Label);
		if (!string.IsNullOrEmpty(text) && matchedLabels != null && matchedLabels.Contains(text))
		{
			return true;
		}
		return ShouldSkipTinyFallbackPlayerTrail(key, trail, recorderCount);
	}

	private static bool ShouldSkipTinyFallbackPlayerTrail(string key, MonsterTrail trail, int recorderCount)
	{
		if (recorderCount > 0 || trail == null || trail.Count >= 30 || trail.HasDeaths)
		{
			return false;
		}
		if (!s_playerTrails.Any((KeyValuePair<string, MonsterTrail> pair) => !string.Equals(pair.Key, key, StringComparison.OrdinalIgnoreCase) && pair.Value != null && pair.Value.Count >= 30))
		{
			return false;
		}
		LogPlayerDebug("skip tiny fallback player trail key=" + key + " label='" + SafeLogToken(trail.Label) + "'#" + trail.Count + " duration=" + trail.DurationSeconds.ToString("0.0") + " threshold=" + 30);
		return true;
	}

	private static bool ShouldUseUnmatchedPlayerTrailFallback(int recorderCount)
	{
		return recorderCount <= 0;
	}

	private static void AddNormalizedPlayerLabel(HashSet<string> labels, string label)
	{
		string text = NormalizePlayerLabelForMatch(label);
		if (!string.IsNullOrEmpty(text))
		{
			labels?.Add(text);
		}
	}

	private static bool HasNormalizedPlayerLabel(HashSet<string> labels, string label)
	{
		string text = NormalizePlayerLabelForMatch(label);
		if (!string.IsNullOrEmpty(text) && labels != null)
		{
			return labels.Contains(text);
		}
		return false;
	}

	private static string NormalizePlayerLabelForMatch(string label)
	{
		if (!IsUsefulPlayerLabel(label))
		{
			return null;
		}
		return label.Trim().ToLowerInvariant();
	}

	private static bool IsPathVisible(string key)
	{
		if (string.IsNullOrEmpty(key))
		{
			return true;
		}
		if (!s_pathVisibility.TryGetValue(key, out var value))
		{
			s_pathVisibility[key] = true;
			return true;
		}
		return value;
	}

	private static void EnsureVisibilityKey(string key)
	{
		if (!string.IsNullOrEmpty(key) && !s_pathVisibility.ContainsKey(key))
		{
			s_pathVisibility[key] = true;
		}
	}

	private static bool DrawSelectedPlayers(object ui, object[] args, float normalizedTime, bool currentOnly)
	{
		s_playerIconDrawRequests.Clear();
		if (args == null || args.Length < 5)
		{
			LogEndMapDiagnostics("player-draw:bad-args", "player draw skipped: AddPlayersToTexture args missing or incomplete");
			return false;
		}
		object obj = args[0];
		if (obj == null)
		{
			LogEndMapDiagnostics("player-draw:no-texture", "player draw skipped: end-map texture is null");
			return false;
		}
		object list = args[1];
		if (!TryConvertInt(GetFieldOrProp(args[3], "x"), out var result) || !TryConvertInt(GetFieldOrProp(args[3], "y"), out var result2) || !TryConvertFloat(args[4], out var result3))
		{
			LogEndMapDiagnostics("player-draw:bad-scale", "player draw skipped: tile offset or pixelsPerTile missing offsetArg=" + DescribeObjectForLog(args[3]) + " pixelsArg=" + ((args[4] == null) ? "null" : args[4].ToString()));
			return false;
		}
		EnsureDrawReflection(ui);
		if (s_addPlayerDotMethod == null || s_color32Ctor == null)
		{
			LogEndMapDiagnostics("player-draw:draw-reflection-missing", "player draw skipped: AddPlayerToTextureAtPosition or Color32 constructor is unavailable addDot=" + (s_addPlayerDotMethod != null) + " color32=" + (s_color32Ctor != null));
			return false;
		}
		float mazeTileSize = GetMazeTileSize();
		if (mazeTileSize <= 0f || result3 <= 0f)
		{
			LogEndMapDiagnostics("player-draw:bad-tile-scale", "player draw skipped: invalid scale tileSize=" + mazeTileSize.ToString("0.00") + " pixelsPerTile=" + result3.ToString("0.00"));
			return false;
		}
		object fieldOrProp = GetFieldOrProp(ui, "playerColors");
		float maxStepWorldSpace = GetMaxStepWorldSpace(ui, mazeTileSize, result3);
		float replayDurationSeconds = GetReplayDurationSeconds();
		int collectionCount = GetCollectionCount(list);
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		List<PlayerIconDrawRequest> playerIconRequests = (ShouldUseMonsterIcons() ? s_playerIconDrawRequests : null);
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		List<string> list2 = (IsPlayerDebugLoggingEnabled() ? new List<string>() : null);
		string localKey = ((list2 == null) ? null : GetLocalPlayerKey());
		for (int i = 0; i < collectionCount; i++)
		{
			string key = "player:" + i;
			if (!TryGetIndexedItem(list, i, out var item) || item == null)
			{
				continue;
			}
			string playerLabel = GetPlayerLabel(ui, item, i);
			string playerRecorderTrailKey = GetPlayerRecorderTrailKey(item, i);
			int rawPositionCount = ((list2 == null) ? (-1) : GetRecorderPositionCount(item, GetFieldOrProp(item, "positions")));
			string text = null;
			MonsterTrail monsterTrail = null;
			if (TryFindMatchingPlayerTrailForRecorder(ui, item, i, playerLabel, hashSet, out var matchedKey, out var trail))
			{
				text = matchedKey;
				monsterTrail = trail;
				if (!string.IsNullOrEmpty(text))
				{
					hashSet.Add(text);
				}
			}
			else if (!string.IsNullOrEmpty(playerRecorderTrailKey))
			{
				hashSet.Add(playerRecorderTrailKey);
			}
			if (!IsPathVisible(key))
			{
				num4++;
				AddPlayerRecorderDebug(list2, i, playerLabel, playerRecorderTrailKey, localKey, text, monsterTrail, rawPositionCount, "hidden");
				AddNormalizedPlayerLabel(hashSet2, playerLabel);
				continue;
			}
			if (HasNormalizedPlayerLabel(hashSet2, playerLabel))
			{
				AddPlayerRecorderDebug(list2, i, playerLabel, playerRecorderTrailKey, localKey, text, monsterTrail, rawPositionCount, "duplicate-label");
				continue;
			}
			object item2 = null;
			if (!TryGetIndexedItem(fieldOrProp, i, out item2))
			{
				item2 = Color32(new TrailColor(240, 240, 240, byte.MaxValue));
			}
			object color = PlayerColor32(item2, new TrailColor(240, 240, 240, byte.MaxValue), currentOnly);
			string text2 = ((!string.IsNullOrEmpty(playerRecorderTrailKey)) ? playerRecorderTrailKey : text);
			CachePlayerIcon(text2, GetFieldOrProp(item, "player"));
			if (DrawPlayerRecorderRaw(ui, obj, item, i, result, result2, result3, mazeTileSize, maxStepWorldSpace, color, normalizedTime, currentOnly, text2, playerIconRequests))
			{
				AddPlayerRecorderDebug(list2, i, playerLabel, playerRecorderTrailKey, localKey, text, monsterTrail, rawPositionCount, "raw-recorder");
				AddNormalizedPlayerLabel(hashSet2, playerLabel);
				num++;
				num2++;
			}
			else if (monsterTrail != null && DrawRecordedPlayerTrail(ui, obj, text, monsterTrail, i, result, result2, result3, mazeTileSize, maxStepWorldSpace, replayDurationSeconds, color, normalizedTime, currentOnly, playerIconRequests))
			{
				AddPlayerRecorderDebug(list2, i, playerLabel, playerRecorderTrailKey, localKey, text, monsterTrail, rawPositionCount, "matched-local-trail");
				int recorderTimelineLastIndex = GetRecorderTimelineLastIndex(item, Clamp01(normalizedTime));
				if (recorderTimelineLastIndex >= 0)
				{
					ShowVisibleDeathIconsForRecorder(ui, item, i, recorderTimelineLastIndex);
				}
				AddNormalizedPlayerLabel(hashSet2, playerLabel);
				num++;
				num3++;
			}
			else
			{
				AddPlayerRecorderDebug(list2, i, playerLabel, playerRecorderTrailKey, localKey, text, monsterTrail, rawPositionCount, "no-output");
			}
		}
		num += DrawUnmatchedRecordedPlayerTrails(ui, obj, result, result2, result3, mazeTileSize, maxStepWorldSpace, replayDurationSeconds, fieldOrProp, normalizedTime, currentOnly, hashSet, hashSet2, collectionCount, playerIconRequests);
		LogPlayerDebug("draw players: recorders=" + collectionCount + " recordedTrails=" + s_playerTrails.Count + " drawn=" + num + " rawRecorders=" + num2 + " recordedFallbacks=" + num3 + " hidden=" + num4 + " currentOnly=" + currentOnly + " time=" + Clamp01(normalizedTime).ToString("0.00") + FormatPlayerRecorderDebugRows(list2));
		if (num == 0 && (collectionCount > 0 || s_playerTrails.Count > 0))
		{
			LogEndMapDiagnostics("player-draw:no-output:" + collectionCount + ":" + s_playerTrails.Count + ":" + currentOnly, "player draw produced no output recorders=" + collectionCount + " recordedTrails=" + s_playerTrails.Count + " currentOnly=" + currentOnly + " time=" + Clamp01(normalizedTime).ToString("0.00") + " duration=" + replayDurationSeconds.ToString("0.00") + " trailKeys=" + GetPlayerTrailDebugSummary());
		}
		return true;
	}

	private static int DrawUnmatchedRecordedPlayerTrails(object ui, object texture, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float maxStepWorldSpace, float replayDurationSeconds, object playerColors, float normalizedTime, bool currentOnly, HashSet<string> drawnKeys, HashSet<string> drawnPlayerLabels, int recorderCount, List<PlayerIconDrawRequest> playerIconRequests)
	{
		if (!ShouldUseUnmatchedPlayerTrailFallback(recorderCount))
		{
			return 0;
		}
		int num = 0;
		int num2 = 0;
		foreach (KeyValuePair<string, MonsterTrail> item2 in from p in s_playerTrails
			orderby p.Value.Count descending, p.Key
			select p)
		{
			if (drawnKeys.Contains(item2.Key) || item2.Value.Count <= 0 || ShouldSkipUnmatchedPlayerTrail(item2.Key, item2.Value, recorderCount, drawnPlayerLabels) || !IsPathVisible(PlayerTrailVisibilityKey(item2.Key)))
			{
				num2++;
				continue;
			}
			object item = null;
			if (!TryGetIndexedItem(playerColors, num2, out item))
			{
				item = Color32(new TrailColor(240, 230, 58, byte.MaxValue));
			}
			if (DrawRecordedPlayerTrail(ui, texture, item2.Key, item2.Value, num2, offsetX, offsetY, pixelsPerTile, tileSize, maxStepWorldSpace, replayDurationSeconds, PlayerColor32(item, new TrailColor(240, 230, 58, byte.MaxValue), currentOnly), normalizedTime, currentOnly, playerIconRequests))
			{
				drawnKeys.Add(item2.Key);
				AddNormalizedPlayerLabel(drawnPlayerLabels, item2.Value.Label);
				num++;
			}
			num2++;
		}
		return num;
	}

	private static bool DrawRecordedPlayerTrail(object ui, object texture, object recorder, int playerIndex, string recorderLabel, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float maxStepWorldSpace, float replayDurationSeconds, object color, float normalizedTime, bool currentOnly, HashSet<string> drawnKeys, List<PlayerIconDrawRequest> playerIconRequests)
	{
		if (!TryFindMatchingPlayerTrailForRecorder(ui, recorder, playerIndex, recorderLabel, drawnKeys, out var matchedKey, out var trail) || trail == null || trail.Count <= 0)
		{
			return false;
		}
		if (!string.IsNullOrEmpty(matchedKey))
		{
			drawnKeys.Add(matchedKey);
		}
		CachePlayerIcon(matchedKey, GetFieldOrProp(recorder, "player"));
		bool num = DrawRecordedPlayerTrail(ui, texture, matchedKey, trail, playerIndex, offsetX, offsetY, pixelsPerTile, tileSize, maxStepWorldSpace, replayDurationSeconds, color, normalizedTime, currentOnly, playerIconRequests);
		float normalizedTime2 = Clamp01(normalizedTime);
		int recorderTimelineLastIndex = GetRecorderTimelineLastIndex(recorder, normalizedTime2);
		if (num && playerIndex >= 0 && recorderTimelineLastIndex >= 0)
		{
			ShowVisibleDeathIconsForRecorder(ui, recorder, playerIndex, recorderTimelineLastIndex);
		}
		return num;
	}

	private static bool DrawRecordedPlayerTrail(object ui, object texture, string key, MonsterTrail trail, int playerIndex, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float maxStepWorldSpace, float replayDurationSeconds, object color, float normalizedTime, bool currentOnly, List<PlayerIconDrawRequest> playerIconRequests)
	{
		float normalizedTime2 = Clamp01(normalizedTime);
		if (currentOnly)
		{
			if (trail.TryGetReplayPoint(normalizedTime2, replayDurationSeconds, out var point))
			{
				if (playerIconRequests != null)
				{
					QueuePlayerIcon(playerIconRequests, key, point, offsetX, offsetY, pixelsPerTile, tileSize, color);
				}
				else
				{
					DrawPathPoint(ui, texture, offsetX, offsetY, pixelsPerTile, tileSize, point.X, point.Z, color, 1f, 2);
				}
			}
			DrawRecordedDeathMarker(ui, texture, trail, playerIndex, offsetX, offsetY, pixelsPerTile, tileSize, normalizedTime2, replayDurationSeconds);
			return true;
		}
		s_replayBuffer.Clear();
		trail.GetReplayPoints(normalizedTime2, replayDurationSeconds, maxStepWorldSpace, s_replayBuffer);
		foreach (WorldPoint item in s_replayBuffer)
		{
			DrawPathPoint(ui, texture, offsetX, offsetY, pixelsPerTile, tileSize, item.X, item.Z, color, normalizedTime2, -1);
		}
		if (playerIconRequests != null && trail.TryGetReplayPoint(normalizedTime2, replayDurationSeconds, out var point2))
		{
			QueuePlayerIcon(playerIconRequests, key, point2, offsetX, offsetY, pixelsPerTile, tileSize, color);
		}
		DrawRecordedDeathMarker(ui, texture, trail, playerIndex, offsetX, offsetY, pixelsPerTile, tileSize, normalizedTime2, replayDurationSeconds);
		return true;
	}

	private static int DrawSelectedMonsters(object ui, object[] args, float normalizedTime)
	{
		int trailIndex;
		return DrawSelectedMonsters(ui, args, normalizedTime, out trailIndex);
	}

	private static int DrawSelectedMonsters(object ui, object[] args, float normalizedTime, out int trailIndex)
	{
		trailIndex = 0;
		if (s_trails.Count == 0)
		{
			LogEndMapDiagnostics("monster-draw:no-trails", "monster draw skipped: no recorded monster trails");
			return 0;
		}
		if (ui == null || args == null || args.Length < 5)
		{
			LogEndMapDiagnostics("monster-draw:bad-args", "monster draw skipped: ui or AddPlayersToTexture args missing");
			return 0;
		}
		object obj = args[0];
		if (obj == null)
		{
			LogEndMapDiagnostics("monster-draw:no-texture", "monster draw skipped: end-map texture is null");
			return 0;
		}
		if (!TryConvertInt(GetFieldOrProp(args[3], "x"), out var result) || !TryConvertInt(GetFieldOrProp(args[3], "y"), out var result2) || !TryConvertFloat(args[4], out var result3))
		{
			LogEndMapDiagnostics("monster-draw:bad-scale", "monster draw skipped: tile offset or pixelsPerTile missing offsetArg=" + DescribeObjectForLog(args[3]) + " pixelsArg=" + ((args[4] == null) ? "null" : args[4].ToString()));
			return 0;
		}
		EnsureDrawReflection(ui);
		if (s_addPlayerDotMethod == null || s_color32Ctor == null)
		{
			LogEndMapDiagnostics("monster-draw:draw-reflection-missing", "monster draw skipped: AddPlayerToTextureAtPosition or Color32 constructor is unavailable addDot=" + (s_addPlayerDotMethod != null) + " color32=" + (s_color32Ctor != null));
			return 0;
		}
		float mazeTileSize = GetMazeTileSize();
		if (mazeTileSize <= 0f || result3 <= 0f)
		{
			LogEndMapDiagnostics("monster-draw:bad-tile-scale", "monster draw skipped: invalid scale tileSize=" + mazeTileSize.ToString("0.00") + " pixelsPerTile=" + result3.ToString("0.00"));
			return 0;
		}
		float maxStepWorldSpace = GetMaxStepWorldSpace(ui, mazeTileSize, result3);
		float replayDurationSeconds = GetReplayDurationSeconds();
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		List<MonsterIconDrawRequest> list = (ShouldUseMonsterIcons() ? new List<MonsterIconDrawRequest>() : null);
		foreach (KeyValuePair<string, MonsterTrail> s_trail in s_trails)
		{
			MonsterTrail value = s_trail.Value;
			if (value.Count == 0)
			{
				num2++;
				continue;
			}
			if (!IsPathVisible(MonsterVisibilityKey(s_trail.Key)))
			{
				num3++;
				continue;
			}
			s_replayBuffer.Clear();
			float normalizedTime2 = Clamp01(normalizedTime);
			WorldPoint point;
			if (s_showFullPath)
			{
				value.GetReplayPoints(normalizedTime2, replayDurationSeconds, maxStepWorldSpace, s_replayBuffer);
			}
			else if (value.TryGetReplayPoint(normalizedTime2, replayDurationSeconds, out point))
			{
				s_replayBuffer.Add(point);
			}
			if (s_replayBuffer.Count == 0)
			{
				num4++;
				continue;
			}
			bool flag = !s_showFullPath;
			TrailColor trailColor = s_palette[value.ColorIndex % s_palette.Length];
			object color = Color32(flag ? OpaqueTrailColor(trailColor) : trailColor);
			float normalizedTime3 = (flag ? 1f : normalizedTime);
			if (list != null && value.TryGetReplayPoint(normalizedTime2, replayDurationSeconds, out var point2))
			{
				int pixelX = FloorToInt(((float)result + point2.X / mazeTileSize + 0.5f) * result3);
				int pixelY = FloorToInt(((float)result2 + point2.Z / mazeTileSize + 0.5f) * result3);
				list.Add(new MonsterIconDrawRequest(pixelX, pixelY, trailIndex + 1, trailColor, value.Label));
			}
			foreach (WorldPoint item in s_replayBuffer)
			{
				int pixelX2 = FloorToInt(((float)result + item.X / mazeTileSize + 0.5f) * result3);
				int pixelY2 = FloorToInt(((float)result2 + item.Z / mazeTileSize + 0.5f) * result3);
				if (!flag || list == null)
				{
					num += DrawTrailDot(ui, obj, pixelX2, pixelY2, color, normalizedTime3, flag ? 2 : (-1));
				}
			}
			trailIndex++;
		}
		if (list != null)
		{
			foreach (MonsterIconDrawRequest item2 in list)
			{
				num += DrawMonsterTextureIcon(ui, obj, item2.PixelX, item2.PixelY, item2.Number, item2.Color, item2.Label);
			}
			s_lastMonsterIconDrawCount = num;
			LogMonsterIconStatus("texture-icons:" + s_showFullPath + ":" + num, "texture icons/path drawn=" + num + " icons=" + list.Count + " trails=" + trailIndex + " fullPath=" + s_showFullPath);
		}
		if (num == 0)
		{
			LogEndMapDiagnostics("monster-draw:no-output:" + s_trails.Count + ":" + trailIndex + ":" + s_showFullPath, "monster draw produced no output trails=" + s_trails.Count + " drawnTrails=" + trailIndex + " skippedEmpty=" + num2 + " skippedHidden=" + num3 + " skippedNoReplay=" + num4 + " fullPath=" + s_showFullPath + " time=" + Clamp01(normalizedTime).ToString("0.00") + " duration=" + replayDurationSeconds.ToString("0.00") + " maxStep=" + maxStepWorldSpace.ToString("0.00") + " iconMode=" + (list != null));
		}
		return num;
	}

	private static bool DrawPlayerRecorderRaw(object ui, object texture, object recorder, int playerIndex, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float maxStepWorldSpace, object color, float timelineNormalizedTime, bool currentOnly, string trailKey, List<PlayerIconDrawRequest> playerIconRequests)
	{
		object fieldOrProp = GetFieldOrProp(recorder, "positions");
		int recorderPositionCount = GetRecorderPositionCount(recorder, fieldOrProp);
		if (recorderPositionCount <= 0)
		{
			return false;
		}
		int num = Math.Max(0, Math.Min(recorderPositionCount - 1, FloorToInt((float)(recorderPositionCount - 1) * Clamp01(timelineNormalizedTime))));
		if (currentOnly)
		{
			if (TryGetRecorderPositionAtIndex(recorder, fieldOrProp, num, out var x, out var y))
			{
				if (playerIconRequests != null)
				{
					QueuePlayerIcon(playerIconRequests, trailKey, new WorldPoint(x, y), offsetX, offsetY, pixelsPerTile, tileSize, color);
				}
				else
				{
					DrawPathPoint(ui, texture, offsetX, offsetY, pixelsPerTile, tileSize, x, y, color, 1f, 2);
				}
			}
			return true;
		}
		bool flag = false;
		float num2 = 0f;
		float num3 = 0f;
		for (int i = 0; i <= num; i++)
		{
			if (TryGetRecorderPositionAtIndex(recorder, fieldOrProp, i, out var x2, out var y2))
			{
				if (!flag || IsPathBreakIndex(recorder, i, num2, num3, x2, y2, maxStepWorldSpace))
				{
					DrawPathPoint(ui, texture, offsetX, offsetY, pixelsPerTile, tileSize, x2, y2, color, Clamp01(timelineNormalizedTime), -1);
					num2 = x2;
					num3 = y2;
					flag = true;
				}
				else
				{
					DrawInterpolatedPathSegment(ui, texture, offsetX, offsetY, pixelsPerTile, tileSize, num2, num3, x2, y2, maxStepWorldSpace, color, Clamp01(timelineNormalizedTime));
					num2 = x2;
					num3 = y2;
				}
			}
		}
		if (playerIconRequests != null && flag)
		{
			QueuePlayerIcon(playerIconRequests, trailKey, new WorldPoint(num2, num3), offsetX, offsetY, pixelsPerTile, tileSize, color);
		}
		ShowVisibleDeathIconsForRecorder(ui, recorder, playerIndex, num);
		return true;
	}

	private static int GetRecorderTimelineLastIndex(object recorder, float normalizedTime)
	{
		object fieldOrProp = GetFieldOrProp(recorder, "positions");
		int recorderPositionCount = GetRecorderPositionCount(recorder, fieldOrProp);
		if (recorderPositionCount <= 0)
		{
			return -1;
		}
		return Math.Max(0, Math.Min(recorderPositionCount - 1, FloorToInt((float)(recorderPositionCount - 1) * Clamp01(normalizedTime))));
	}

	private static int GetRecorderPositionCount(object recorder, object positions)
	{
		int num = GetCollectionCount(positions);
		if (num <= 0 && TryConvertInt(GetFieldOrProp(recorder, "Count"), out var result))
		{
			num = result;
		}
		return Math.Max(0, num);
	}

	private static bool TryGetRecorderPositionAtIndex(object recorder, object positions, int index, out float x, out float y)
	{
		x = 0f;
		y = 0f;
		if (recorder == null || index < 0)
		{
			return false;
		}
		if (s_getPositionAtIndexMethod == null || !s_getPositionAtIndexMethod.DeclaringType.IsInstanceOfType(recorder))
		{
			s_getPositionAtIndexMethod = recorder.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetPositionAtIndex" && m.GetParameters().Length == 1);
		}
		if (s_getPositionAtIndexMethod != null)
		{
			try
			{
				if (TryReadVectorXY(s_getPositionAtIndexMethod.Invoke(recorder, new object[1] { index }), out x, out y))
				{
					return true;
				}
			}
			catch
			{
			}
		}
		if (TryGetIndexedItem(positions, index, out var item))
		{
			return TryReadVectorXY(item, out x, out y);
		}
		return false;
	}

	private static bool IsPathBreakIndex(object recorder, int index, float previousX, float previousY, float x, float y, float maxStepWorldSpace)
	{
		if (IsTeleportationIndex(recorder, index))
		{
			return true;
		}
		float num = x - previousX;
		float num2 = y - previousY;
		return (float)Math.Sqrt(num * num + num2 * num2) > GetPlayerTeleportBreakDistance(recorder, maxStepWorldSpace);
	}

	private static float GetPlayerTeleportBreakDistance(object recorder, float maxStepWorldSpace)
	{
		if (TryConvertFloat(GetFieldOrProp(recorder, "minTeleportationDistance"), out var result) && result > 0f)
		{
			return result;
		}
		return Math.Max(15f, maxStepWorldSpace * 24f);
	}

	private static bool IsTeleportationIndex(object recorder, int index)
	{
		if (index <= 0)
		{
			return false;
		}
		object fieldOrProp = GetFieldOrProp(recorder, "teleportationIndexes");
		int collectionCount = GetCollectionCount(fieldOrProp);
		for (int i = 0; i < collectionCount; i++)
		{
			if (TryGetIndexedItem(fieldOrProp, i, out var item) && TryConvertInt(item, out var result) && result == index)
			{
				return true;
			}
		}
		return false;
	}

	private static void DrawInterpolatedPathSegment(object ui, object texture, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float fromX, float fromY, float toX, float toY, float maxStepWorldSpace, object color, float normalizedTime)
	{
		float num = toX - fromX;
		float num2 = toY - fromY;
		float num3 = (float)Math.Sqrt(num * num + num2 * num2);
		int num4 = Math.Max(1, (int)Math.Ceiling(num3 / Math.Max(0.25f, maxStepWorldSpace)));
		for (int i = 1; i <= num4; i++)
		{
			float num5 = (float)i / (float)num4;
			DrawPathPoint(ui, texture, offsetX, offsetY, pixelsPerTile, tileSize, fromX + num * num5, fromY + num2 * num5, color, normalizedTime, -1);
		}
	}

	private static void ShowVisibleDeathIconsForRecorder(object ui, object recorder, int playerIndex, int lastPositionIndex)
	{
		if (playerIndex < 0 || recorder == null || lastPositionIndex < 0)
		{
			return;
		}
		object fieldOrProp = GetFieldOrProp(recorder, "deathIndexes");
		int collectionCount = GetCollectionCount(fieldOrProp);
		for (int i = 0; i < collectionCount; i++)
		{
			if (TryGetIndexedItem(fieldOrProp, i, out var item) && TryConvertInt(item, out var result) && result <= lastPositionIndex)
			{
				ShowDeathIcon(ui, playerIndex, i);
			}
		}
	}

	private static void DrawPathPoint(object ui, object texture, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float worldX, float worldY, object color, float normalizedTime, int extraOverride)
	{
		int pixelX = FloorToInt(((float)offsetX + worldX / tileSize + 0.5f) * pixelsPerTile);
		int pixelY = FloorToInt(((float)offsetY + worldY / tileSize + 0.5f) * pixelsPerTile);
		DrawTrailDot(ui, texture, pixelX, pixelY, color, normalizedTime, extraOverride);
	}

	private static void DrawRecordedDeathMarker(object ui, object texture, MonsterTrail trail, int playerIndex, int offsetX, int offsetY, float pixelsPerTile, float tileSize, float normalizedTime, float replayDurationSeconds)
	{
		if (trail != null && playerIndex >= 0)
		{
			trail.GetVisibleDeathIndexes(normalizedTime, replayDurationSeconds, s_replayDeathIndexes);
			for (int i = 0; i < s_replayDeathIndexes.Count; i++)
			{
				ShowDeathIcon(ui, playerIndex, s_replayDeathIndexes[i]);
			}
		}
	}

	private static bool ClearPathTexture(object ui, object texture)
	{
		if (texture == null)
		{
			return false;
		}
		if (TryClearTextureWithGameCoroutine(ui, texture))
		{
			return true;
		}
		if (!EnsureTextureClearReflection(texture))
		{
			return false;
		}
		if (!TryConvertInt(s_textureWidthProperty?.GetValue(texture), out var result) || !TryConvertInt(s_textureHeightProperty?.GetValue(texture), out var result2) || result <= 0 || result2 <= 0)
		{
			return false;
		}
		if (!s_textureSetPixelsFailed && s_textureSetPixels != null && TryClearTextureWithSetPixels(texture, result, result2))
		{
			return true;
		}
		if (s_textureSetPixel != null && TryClearTextureWithSetPixel(texture, result, result2))
		{
			return true;
		}
		if (!s_clearWarningLogged)
		{
			s_clearWarningLogged = true;
			MelonLogger.Warning("Could not clear end-map path texture; falling back to game drawing.");
		}
		return false;
	}

	private static bool TryClearTextureWithGameCoroutine(object ui, object texture)
	{
		if (ui == null || texture == null)
		{
			return false;
		}
		try
		{
			if (s_clearTextureCoroutineMethod == null || !s_clearTextureCoroutineMethod.DeclaringType.IsInstanceOfType(ui))
			{
				s_clearTextureCoroutineMethod = ui.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "ClearTextureCoroutine" && m.GetParameters().Length >= 1);
			}
			if (s_clearTextureCoroutineMethod == null)
			{
				return false;
			}
			object[] parameters = ((s_clearTextureCoroutineMethod.GetParameters().Length != 1) ? new object[2]
			{
				texture,
				(byte)0
			} : new object[1] { texture });
			if (!(s_clearTextureCoroutineMethod.Invoke(ui, parameters) is IEnumerator enumerator))
			{
				return false;
			}
			s_allowTextureClearCoroutine = true;
			try
			{
				int num = 0;
				while (enumerator.MoveNext() && num++ < 16)
				{
				}
			}
			finally
			{
				s_allowTextureClearCoroutine = false;
				(enumerator as IDisposable)?.Dispose();
			}
			return true;
		}
		catch
		{
			s_allowTextureClearCoroutine = false;
			return false;
		}
	}

	private static bool TryClearTextureWithSetPixels(object texture, int width, int height)
	{
		try
		{
			int num = width * height;
			if (s_clearPixels == null || s_clearPixelsWidth != width || s_clearPixelsHeight != height || s_clearPixels.Length != num)
			{
				s_clearPixels = Array.CreateInstance(s_colorType, num);
				s_clearPixelsWidth = width;
				s_clearPixelsHeight = height;
				for (int i = 0; i < num; i++)
				{
					s_clearPixels.SetValue(s_clearColor, i);
				}
			}
			s_textureSetPixels.Invoke(texture, new object[1] { s_clearPixels });
			return true;
		}
		catch
		{
			s_textureSetPixelsFailed = true;
			return false;
		}
	}

	private static bool TryClearTextureWithSetPixel(object texture, int width, int height)
	{
		try
		{
			for (int i = 0; i < height; i++)
			{
				for (int j = 0; j < width; j++)
				{
					s_textureSetPixel.Invoke(texture, new object[3] { j, i, s_clearColor });
				}
			}
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static bool EnsureTextureClearReflection(object texture)
	{
		try
		{
			if (s_colorType == null)
			{
				s_colorType = FindType("UnityEngine.Color");
			}
			if (s_colorCtor == null && s_colorType != null)
			{
				s_colorCtor = s_colorType.GetConstructor(new Type[4]
				{
					typeof(float),
					typeof(float),
					typeof(float),
					typeof(float)
				});
			}
			if (s_colorType == null || s_colorCtor == null)
			{
				return false;
			}
			if (s_clearColor == null)
			{
				s_clearColor = Color(0f, 0f, 0f, 0f);
			}
			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);
			}
			if ((object)s_textureSetPixels == null)
			{
				s_textureSetPixels = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "SetPixels" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsArray && m.GetParameters()[0].ParameterType.GetElementType() == s_colorType);
			}
			if ((object)s_textureSetPixel == null)
			{
				s_textureSetPixel = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "SetPixel" && m.GetParameters().Length == 3 && m.GetParameters()[0].ParameterType == typeof(int) && m.GetParameters()[1].ParameterType == typeof(int) && m.GetParameters()[2].ParameterType == s_colorType);
			}
			if ((object)s_textureApply == null)
			{
				s_textureApply = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Apply" && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == typeof(bool) && m.GetParameters()[1].ParameterType == typeof(bool));
			}
			return s_textureWidthProperty != null && s_textureHeightProperty != null && (s_textureSetPixels != null || s_textureSetPixel != null);
		}
		catch
		{
			return false;
		}
	}

	private static void ApplyTexture(object texture)
	{
		if (texture == null)
		{
			return;
		}
		try
		{
			if (s_textureApply == null)
			{
				EnsureTextureClearReflection(texture);
			}
			s_textureApply?.Invoke(texture, new object[2] { false, false });
		}
		catch
		{
		}
	}

	private static bool BeginPathTextureBatch(object texture)
	{
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		AbortPathTextureBatch();
		try
		{
			Texture2D val = (Texture2D)((texture is Texture2D) ? texture : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			int width = ((Texture)val).width;
			int height = ((Texture)val).height;
			if (width <= 0 || height <= 0)
			{
				return false;
			}
			int num = width * height;
			if (s_batchPixels == null || s_batchWidth != width || s_batchHeight != height || ((Il2CppArrayBase<Color32>)(object)s_batchPixels).Length != num)
			{
				s_batchPixels = new Il2CppStructArray<Color32>((long)num);
				s_batchWidth = width;
				s_batchHeight = height;
			}
			Color32 val2 = default(Color32);
			((Color32)(ref val2))..ctor((byte)0, (byte)0, (byte)0, (byte)0);
			for (int i = 0; i < num; i++)
			{
				((Il2CppArrayBase<Color32>)(object)s_batchPixels)[i] = val2;
			}
			s_batchTexture = val;
			s_batchDrawActive = true;
			return true;
		}
		catch (Exception ex)
		{
			LogBatchWarning("batch texture init failed: " + ex.GetType().Name + " " + ex.Message);
			AbortPathTextureBatch();
			return false;
		}
	}

	private static bool FinishPathTextureBatch()
	{
		if (!s_batchDrawActive || (Object)(object)s_batchTexture == (Object)null || s_batchPixels == null)
		{
			return false;
		}
		try
		{
			s_batchTexture.SetPixels32(s_batchPixels);
			s_batchTexture.Apply(false, false);
			AbortPathTextureBatch();
			return true;
		}
		catch (Exception ex)
		{
			LogBatchWarning("batch texture upload failed: " + ex.GetType().Name + " " + ex.Message);
			AbortPathTextureBatch();
			return false;
		}
	}

	private static void AbortPathTextureBatch()
	{
		s_batchDrawActive = false;
		s_batchTexture = null;
	}

	private static bool TryDrawBatchPixel(object texture, int x, int y, object color)
	{
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		if (!s_batchDrawActive || (Object)(object)s_batchTexture == (Object)null || s_batchPixels == null || texture != s_batchTexture)
		{
			return false;
		}
		if (x < 0 || y < 0 || x >= s_batchWidth || y >= s_batchHeight)
		{
			return true;
		}
		TrailColor trailColor = TrailColorFromColor32(color, new TrailColor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue));
		((Il2CppArrayBase<Color32>)(object)s_batchPixels)[y * s_batchWidth + x] = new Color32(trailColor.R, trailColor.G, trailColor.B, trailColor.A);
		return true;
	}

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

	private static void HideAllDeathIcons(object ui)
	{
		object fieldOrProp = GetFieldOrProp(ui, "deathIcons");
		if (fieldOrProp == null)
		{
			return;
		}
		ForEachListItem(fieldOrProp, delegate(object entry)
		{
			object obj = GetFieldOrProp(entry, "Value") ?? GetFieldOrProp(entry, "value");
			if (obj == null)
			{
				obj = entry;
			}
			ForEachListItem(obj, delegate(object icon)
			{
				InvokeNoArg(icon, "Hide");
			});
		});
	}

	private static void ShowDeathIcon(object ui, int playerIndex, int deathIndex)
	{
		object fieldOrProp = GetFieldOrProp(ui, "deathIcons");
		if (fieldOrProp != null && TryGetIndexedItem(fieldOrProp, playerIndex, out var item) && item != null && TryGetIndexedItem(item, deathIndex, out var item2) && item2 != null)
		{
			InvokeNoArg(item2, "Show");
		}
	}

	private static void InvokeNoArg(object obj, string methodName)
	{
		if (obj == null)
		{
			return;
		}
		try
		{
			obj.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)?.Invoke(obj, Array.Empty<object>());
		}
		catch
		{
		}
	}

	private static int DrawTrailDot(object ui, object texture, int pixelX, int pixelY, object color, float normalizedTime, int extraOverride = -1)
	{
		int num = ((extraOverride >= 0) ? extraOverride : 0);
		int num2 = 0;
		for (int i = -num; i <= num; i++)
		{
			for (int j = -num; j <= num; j++)
			{
				if (j * j + i * i <= num * num)
				{
					int num3 = pixelX + j;
					int num4 = pixelY + i;
					if (!TryDrawBatchPixel(texture, num3, num4, color))
					{
						s_addPlayerDotMethod.Invoke(ui, new object[5] { texture, num3, num4, color, normalizedTime });
					}
					num2++;
				}
			}
		}
		return num2;
	}

	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 = FloorToInt(((float)offsetX + point.X / tileSize + 0.5f) * pixelsPerTile);
			int pixelY = 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 = GetFieldOrProp(GetPlayerClientData(player), "Avatar");
		Texture2D val = (Texture2D)((fieldOrProp is Texture2D) ? fieldOrProp : null);
		if ((Object)(object)val != (Object)null)
		{
			return val;
		}
		object fieldOrProp2 = 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 (TryConvertFloat(GetFieldOrProp(value, "x"), out var result) && TryConvertFloat(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 lef