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 _points = new List(); private readonly HashSet _segmentStarts = new HashSet(); private readonly List _deathIndexes = new List(); 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 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 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 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 s_trails = new Dictionary(); private static readonly Dictionary s_playerTrails = new Dictionary(); private static readonly Dictionary s_playerIcons = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_playerIconRetryUtc = new Dictionary(StringComparer.Ordinal); private static readonly List s_monsterLookupBuffer = new List(); private static readonly List s_playerLookupBuffer = new List(); private static readonly List s_replayBuffer = new List(); private static readonly List s_replayDeathIndexes = new List(); private static readonly List s_playerIconDrawRequests = new List(); private static readonly List s_playerEntries = new List(); private static readonly List s_monsterEntries = new List(); private static readonly List s_nameListEntries = new List(); private static readonly List s_extraNameListRows = new List(); private static readonly Dictionary s_nameListRowTransformStates = new Dictionary(); private static readonly Dictionary s_nextNameListDiagnosticsBySignature = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_nextRectTransformDiagnosticsBySignature = new Dictionary(StringComparer.Ordinal); private static readonly List s_cachedGameObjectFallbackMonsters = new List(); private static readonly Dictionary s_pathVisibility = new Dictionary(); private static readonly Dictionary s_monsterIcons = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Dictionary s_knownMonsterObjectNameMap; private static readonly Dictionary s_monsterIconAliases = new Dictionary(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 s_verboseLogs; private static MelonPreferences_Entry 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 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("VerboseLogs", false, "Log detailed MonsterPathVisualisation diagnostics.", (string)null, false, false, (ValueValidator)null, (string)null); s_debugPlayerPath = obj.CreateEntry("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 hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(StringComparer.OrdinalIgnoreCase); int num = collectionCount; foreach (KeyValuePair 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 item in s_trails.OrderBy((KeyValuePair 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 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 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 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 labels, string label) { string text = NormalizePlayerLabelForMatch(label); if (!string.IsNullOrEmpty(text)) { labels?.Add(text); } } private static bool HasNormalizedPlayerLabel(HashSet 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 hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(StringComparer.OrdinalIgnoreCase); List playerIconRequests = (ShouldUseMonsterIcons() ? s_playerIconDrawRequests : null); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; List list2 = (IsPlayerDebugLoggingEnabled() ? new List() : 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 drawnKeys, HashSet drawnPlayerLabels, int recorderCount, List playerIconRequests) { if (!ShouldUseUnmatchedPlayerTrailFallback(recorderCount)) { return 0; } int num = 0; int num2 = 0; foreach (KeyValuePair 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 drawnKeys, List 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 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 list = (ShouldUseMonsterIcons() ? new List() : null); foreach (KeyValuePair 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 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)(object)s_batchPixels).Length != num) { s_batchPixels = new Il2CppStructArray((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)(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)(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()); } 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 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 pixels = source.GetPixels32(); if (pixels == null || ((Il2CppArrayBase)(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)(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 left; } if (num3 > num4) { return upperLeft; } return up; } private static void DrawControlPanel() { float scale = UiScale(); BeginScaledImguiFont(scale); try { float height = S(84f, scale); GetControlPanelLayout(height, out var x, out var y, out var width); DrawSolid(x, y, width, height, Color(0.02f, 0.02f, 0.02f, 0.58f)); if (Button(x + S(10f, scale), y + S(8f, scale), S(86f, scale), S(22f, scale), s_paused ? "Resume" : "Pause")) { s_paused = !s_paused; ResetPlaybackClock(); RedrawLastTexture(); } if (Button(x + S(104f, scale), y + S(8f, scale), S(124f, scale), S(22f, scale), s_showFullPath ? "Full path" : "Current dots")) { s_showFullPath = !s_showFullPath; RedrawLastTexture(); } if (Button(x + S(238f, scale), y + S(8f, scale), S(96f, scale), S(22f, scale), ShouldUseMonsterIcons() ? "Icons on" : "Icons off")) { bool flag = (s_monsterIconsEnabled = !ShouldUseMonsterIcons()); s_lastMonsterIconDrawCount = 0; LogMonsterIconStatus("toggle:" + flag, "enabled=" + flag + " fullPath=" + s_showFullPath + " trails=" + s_trails.Count); RedrawLastTexture(); } Label(x + S(10f, scale), y + S(36f, scale), S(48f, scale), S(20f, scale), "speed"); DrawPlaybackSpeedButton(x + S(60f, scale), y + S(34f, scale), S(46f, scale), S(22f, scale), 1f, "1x"); DrawPlaybackSpeedButton(x + S(112f, scale), y + S(34f, scale), S(56f, scale), S(22f, scale), 0.5f, "0.5x"); DrawPlaybackSpeedButton(x + S(174f, scale), y + S(34f, scale), S(66f, scale), S(22f, scale), 0.25f, "0.25x"); DrawPlaybackSpeedButton(x + S(246f, scale), y + S(34f, scale), S(56f, scale), S(22f, scale), 0.1f, "0.1x"); Label(x + S(314f, scale), y + S(36f, scale), S(92f, scale), S(20f, scale), "time " + (int)(Clamp01(s_scrubTime) * 100f) + "%"); float num = HorizontalSlider(x + S(10f, scale), y + S(60f, scale), width - S(20f, scale), S(18f, scale), Clamp01(s_scrubTime), 0f, 1f); if (Math.Abs(num - s_scrubTime) > 0.001f) { s_scrubTime = Clamp01(num); s_paused = true; ResetPlaybackClock(); RedrawLastTexture(); } DrawNameList("ongui"); } finally { EndScaledImguiFont(); } } private static bool ShouldUseMonsterIcons() { return s_monsterIconsEnabled; } private static void DrawPlaybackSpeedButton(float x, float y, float width, float height, float speed, string label) { bool flag = Math.Abs(s_playbackSpeed - speed) < 0.001f; if (Button(x, y, width, height, flag ? ("[" + label + "]") : label)) { SetPlaybackSpeed(speed); } } private static void SetPlaybackSpeed(float speed) { s_playbackSpeed = Clamp(speed, 0.05f, 4f); ResetPlaybackClock(); } private static void ResetPlaybackClock() { s_lastPlaybackUpdateUtc = DateTime.MinValue; s_nextPlaybackRedrawUtc = DateTime.MinValue; s_playbackEndHoldUntilUtc = DateTime.MinValue; } private static void GetControlPanelLayout(float height, out float x, out float y, out float width) { float scale = UiScale(); x = S(70f, scale); y = S(12f, scale); width = Math.Min(500f, (float)ScreenWidth() - x * 2f); if (TryGetPathImageGuiRect(s_lastUiInstance, out var x2, out var y2, out var width2, out var _)) { float val = S(500f, scale); float val2 = S(460f, scale); float num = Math.Min(val, Math.Max(val2, width2)); float value = x2 + (width2 - num) * 0.5f; float num2 = y2 - height - S(8f, scale); if (!(num2 < S(8f, scale))) { width = Math.Min(num, (float)ScreenWidth() - S(24f, scale)); x = Clamp(value, S(12f, scale), Math.Max(S(12f, scale), (float)ScreenWidth() - width - S(12f, scale))); y = num2; } } } private static bool TryGetPathImageGuiRect(object ui, out float x, out float y, out float width, out float height) { x = 0f; y = 0f; width = 0f; height = 0f; try { object fieldOrProp = GetFieldOrProp(ui, "pathImage"); RawImage val = (RawImage)((fieldOrProp is RawImage) ? fieldOrProp : null); RectTransform val2 = (((Object)(object)val == (Object)null) ? null : ((Graphic)val).rectTransform); if ((Object)(object)val2 == (Object)null) { return false; } object fieldOrProp2 = GetFieldOrProp(val2, "rect"); object fieldOrProp3 = GetFieldOrProp(val2, "position"); object fieldOrProp4 = GetFieldOrProp(val2, "pivot"); object fieldOrProp5 = GetFieldOrProp(val2, "lossyScale"); if (!TryConvertFloat(GetFieldOrProp(fieldOrProp2, "width"), out var result) || !TryConvertFloat(GetFieldOrProp(fieldOrProp2, "height"), out var result2) || !TryConvertFloat(GetFieldOrProp(fieldOrProp3, "x"), out var result3) || !TryConvertFloat(GetFieldOrProp(fieldOrProp3, "y"), out var result4) || !TryConvertFloat(GetFieldOrProp(fieldOrProp4, "x"), out var result5) || !TryConvertFloat(GetFieldOrProp(fieldOrProp4, "y"), out var result6)) { return false; } float result7; float num = (TryConvertFloat(GetFieldOrProp(fieldOrProp5, "x"), out result7) ? Math.Abs(result7) : 1f); float result8; float num2 = (TryConvertFloat(GetFieldOrProp(fieldOrProp5, "y"), out result8) ? Math.Abs(result8) : 1f); float num3 = Math.Abs(result * num); float num4 = Math.Abs(result2 * num2); float num5 = result3 - result5 * num3; float num6 = num5 + num3; float num7 = result4 - result6 * num4; float num8 = num7 + num4; if (num6 <= num5 || num8 <= num7) { return false; } x = num5; y = (float)ScreenHeight() - num8; width = num6 - num5; height = num8 - num7; return width > 40f && height > 40f; } catch { return false; } } private static bool TryGetTextureDimensions(object texture, out int width, out int height) { width = 0; height = 0; if (texture == null) { return false; } try { Type type = texture.GetType(); if ((object)s_textureWidthProperty == null) { s_textureWidthProperty = type.GetProperty("width", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_textureHeightProperty == null) { s_textureHeightProperty = type.GetProperty("height", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } return TryConvertInt(s_textureWidthProperty?.GetValue(texture), out width) && TryConvertInt(s_textureHeightProperty?.GetValue(texture), out height) && width > 0 && height > 0; } catch { return false; } } private static void DrawNameList(string source) { List nameListEntries = GetNameListEntries(); if (nameListEntries.Count > 0) { SyncGameNameList(s_lastUiInstance, nameListEntries, source); } } private static List GetNameListEntries() { s_nameListEntries.Clear(); s_nameListEntries.AddRange(s_playerEntries); s_nameListEntries.AddRange(s_monsterEntries); return s_nameListEntries; } private static void HandleNameListClickProbe(DateTime now) { if (s_lastUiInstance == null || (now - s_lastEndMapDrawUtc).TotalSeconds > 30.0 || !TryGetMouseButtonDown(out var x, out var y, out var source)) { return; } List nameListEntries = GetNameListEntries(); if (nameListEntries.Count <= 0) { return; } object fieldOrProp = GetFieldOrProp(s_lastUiInstance, "playerNameUIs"); int num = Math.Min(nameListEntries.Count, GetCollectionCount(fieldOrProp) + GetExtraNameListRowCount(s_lastUiInstance)); if (num <= 0) { return; } int count = nameListEntries.Count; int columnCount = Math.Max(1, (int)Math.Ceiling((float)count / 5f)); float uiScale = NameListScale(Math.Min(5, count), columnCount); int num2 = Math.Min(nameListEntries.Count, num); float num3 = x; float num4 = (float)ScreenHeight() - y; int num5 = 0; int num6 = 0; int num7 = -1; List rows = (IsVerboseLoggingEnabled() ? new List() : null); for (int i = 0; i < num2; i++) { if (TryGetGameNameListClickTargetRect(i, uiScale, out var x2, out var y2, out var width, out var height, out var source2)) { num5++; bool flag = num3 >= x2 && num3 <= x2 + width && num4 >= y2 && num4 <= y2 + height; AddNameListClickDiagnostic(rows, i, nameListEntries[i].Label, x2, y2, width, height, flag, source2); if (flag && num7 < 0) { num7 = i; } } else { num6++; } } if (num7 >= 0) { PathEntry pathEntry = nameListEntries[num7]; s_pathVisibility[pathEntry.Key] = !IsPathVisible(pathEntry.Key); RedrawLastTexture(); } MelonLogger.Msg("[NameListClickProbe] mouse=" + (int)num3 + "," + (int)num4 + " raw=" + (int)x + "," + (int)y + " input=" + source + " entries=" + nameListEntries.Count + " synced=" + num + " candidates=" + num5 + " missingRects=" + num6 + " hit=" + ((num7 >= 0) ? (num7 + " '" + SafeLogToken(nameListEntries[num7].Label) + "'") : "none") + FormatNameListClickDiagnostics(rows)); } private static bool TryGetMouseButtonDown(out float x, out float y, out string source) { x = 0f; y = 0f; source = "none"; if (!EnsureUnityReflection()) { source = "unity-reflection-not-ready"; return false; } if (s_inputType == null || s_inputGetMouseButtonDown == null || s_inputMousePositionProperty == null) { source = "input-reflection-missing"; LogNameListDiagnostics("click-input-missing", "click probe input unavailable inputType=" + ((s_inputType == null) ? "none" : s_inputType.FullName) + " getMouseButtonDown=" + ((s_inputGetMouseButtonDown == null) ? "none" : "ok") + " mousePosition=" + ((s_inputMousePositionProperty == null) ? "none" : "ok")); return false; } try { if (!Convert.ToBoolean(s_inputGetMouseButtonDown.Invoke(null, new object[1] { 0 }))) { return false; } object value = s_inputMousePositionProperty.GetValue(null); if (!TryConvertFloat(GetFieldOrProp(value, "x"), out x) || !TryConvertFloat(GetFieldOrProp(value, "y"), out y)) { source = "mouse-position-unreadable"; MelonLogger.Msg("[NameListClickProbe] input=mouse-position-unreadable type=" + ((value == null) ? "none" : value.GetType().FullName)); return false; } source = "UnityEngine.Input(reflection)"; return true; } catch (Exception ex) { source = "input-exception"; LogNameListDiagnostics("click-input-exception:" + ex.GetType().Name, "click probe input failed: " + ex.GetType().Name + " " + ex.Message); return false; } } private static bool TryGetGameNameListClickTargetRect(int index, float uiScale, out float x, out float y, out float width, out float height, out string source) { source = "none"; float x2; float y2; float width2; float height2; bool flag = TryGetGameNameListElementGuiRect(index, out x2, out y2, out width2, out height2); if (TryGetGameNameListTextGuiRect(index, out var x3, out var y3, out var width3, out var height3)) { float num = Math.Max(height3 + S(8f, uiScale), S(22f, uiScale)); x = Math.Max(0f, x3 - num); y = y3; width = width3 + num; height = height3; if (flag && width2 > 1f && height2 > 1f) { UnionGuiRects(ref x, ref y, ref width, ref height, x2, y2, Math.Min(width2, S(260f, uiScale)), height2); ExpandGuiRect(ref x, ref y, ref width, ref height, S(4f, uiScale), S(3f, uiScale)); source = "element-text-union"; if (width > 1f) { return height > 1f; } return false; } source = "text-expanded-left"; if (width > 1f) { return height > 1f; } return false; } if (flag) { x = x2; y = y2; width = Math.Min(width2, S(260f, uiScale)); height = height2; ExpandGuiRect(ref x, ref y, ref width, ref height, S(4f, uiScale), S(3f, uiScale)); source = "element"; if (width > 1f) { return height > 1f; } return false; } x = 0f; y = 0f; width = 0f; height = 0f; return false; } private static void UnionGuiRects(ref float x, ref float y, ref float width, ref float height, float otherX, float otherY, float otherWidth, float otherHeight) { float num = Math.Min(x, otherX); float num2 = Math.Min(y, otherY); float num3 = Math.Max(x + width, otherX + otherWidth); float num4 = Math.Max(y + height, otherY + otherHeight); x = num; y = num2; width = Math.Max(0f, num3 - num); height = Math.Max(0f, num4 - num2); } private static void ExpandGuiRect(ref float x, ref float y, ref float width, ref float height, float horizontal, float vertical) { x = Math.Max(0f, x - Math.Max(0f, horizontal)); y = Math.Max(0f, y - Math.Max(0f, vertical)); width += Math.Max(0f, horizontal) * 2f; height += Math.Max(0f, vertical) * 2f; } private static bool TryGetGameNameListElementGuiRect(int index, out float x, out float y, out float width, out float height) { x = 0f; y = 0f; width = 0f; height = 0f; object fieldOrProp = GetFieldOrProp(s_lastUiInstance, "playerNameUIs"); int collectionCount = GetCollectionCount(fieldOrProp); string source; if (index >= collectionCount) { ExtraNameListRow extraNameListRow = GetExtraNameListRow(index - collectionCount); if (extraNameListRow != null) { return TryGetObjectGuiRect(extraNameListRow.Element, out x, out y, out width, out height, out source); } return false; } if (!TryGetIndexedItem(fieldOrProp, index, out var item) || item == null) { return false; } return TryGetObjectGuiRect(GetFieldOrProp(item, "element"), out x, out y, out width, out height, out source); } private static int SyncGameNameList(object ui, string source = "direct") { return SyncGameNameList(ui, GetNameListEntries(), source); } private static int SyncGameNameList(object ui, IReadOnlyList entries, string source = "direct") { //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) if (ui == null) { return 0; } object fieldOrProp = GetFieldOrProp(ui, "playerNameUIs"); int collectionCount = GetCollectionCount(fieldOrProp); if (collectionCount <= 0) { return 0; } int num = entries?.Count ?? 0; if (s_extraNameListUi != null && s_extraNameListUi != ui) { ClearExtraNameListRows(); } bool flag = SyncNameListSecondaryPanel(ui, fieldOrProp, num); int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; List rows = (IsVerboseLoggingEnabled() ? new List() : null); for (int i = 0; i < collectionCount; i++) { if (!TryGetIndexedItem(fieldOrProp, i, out var item) || item == null) { continue; } object fieldOrProp2 = GetFieldOrProp(item, "element"); object fieldOrProp3 = GetFieldOrProp(item, "playerNameText"); if (i >= num) { SetTextValue(fieldOrProp3, string.Empty); SetGameObjectActive(fieldOrProp2, active: false); continue; } PathEntry pathEntry = entries[i]; bool flag2 = IsPathVisible(pathEntry.Key); SetGameObjectActive(fieldOrProp2, active: true); if (SetTextValue(fieldOrProp3, pathEntry.Label ?? string.Empty)) { num3++; } else { num4++; } SetTextColorValue(fieldOrProp3, flag2 ? new Color(0.88f, 0.88f, 0.86f, 1f) : new Color(0.42f, 0.42f, 0.42f, 1f)); if (SetNameListElementMarkerColor(fieldOrProp2, pathEntry.Color, flag2)) { num5++; } if (num > 8 && i >= 4 && TryGetNameListRowRectTransform(fieldOrProp2, out var rectTransform)) { PositionExpandedNameListRow(fieldOrProp, collectionCount, i, rectTransform); } PrimeNameListRowGeometry(fieldOrProp2, fieldOrProp3); AddNameListTextDiagnostic(rows, i, pathEntry.Label, GetNameListTextDiagnostic(fieldOrProp3)); num2++; } int num6 = Math.Min(collectionCount, num); int num7 = ((num6 >= num) ? HideExtraNameListRows() : (flag ? SyncExtraNameListRows(ui, fieldOrProp, entries, num6) : HideExtraNameListRows())); LogNameListDiagnostics("sync:" + collectionCount + ":" + num + ":" + num2 + ":" + num7 + ":" + num3 + ":" + num4, "name list sync slots=" + collectionCount + " source=" + SafeLogToken(source) + " entries=" + num + " synced=" + num2 + " extraRows=" + num7 + " textSet=" + num3 + " textFailed=" + num4 + " markerSet=" + num5 + FormatNameListTextDiagnostics(rows)); MaybeLogNameListUiStructure(ui, collectionCount, num); LogNameListPlacementDiagnostics(source, ui, fieldOrProp, entries, collectionCount, num7); return Math.Min(num6 + num7, num); } private static bool SyncNameListSecondaryPanel(object ui, object playerNameUis, int entryCount) { //IL_025d: Unknown result type (might be due to invalid IL or missing references) object fieldOrProp = GetFieldOrProp(ui, "playerNameListSecondaryPanel"); GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); if ((Object)(object)val == (Object)null) { LogNameListDiagnostics("secondary-panel:none:" + entryCount, "name list secondary panel unavailable entries=" + entryCount); return entryCount <= 8; } SetGameObjectActive(val, entryCount > 4); Transform transform = val.transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)val2 == (Object)null) { val2 = val.GetComponent(); } if ((Object)(object)val2 == (Object)null) { LogNameListDiagnostics("secondary-panel:no-rect:" + entryCount + ":" + SafeLogToken(((Object)val).name), "name list secondary panel has no RectTransform entries=" + entryCount + " panel=" + SafeLogToken(DescribeUnityObject(val)) + " components=" + SafeLogToken(DescribeGameObjectComponents(val))); return entryCount <= 8; } int instanceID = ((Object)val).GetInstanceID(); if (s_nameListSecondaryPanelState == null || s_nameListSecondaryPanelState.InstanceId != instanceID) { s_nameListSecondaryPanelState = new NameListSecondaryPanelState(val, val2); ResetNameListRowSpacingCache(instanceID); ResetNameListRowTransformStates(); } else { s_nameListSecondaryPanelState.Restore(val2); } if (entryCount <= 8) { LogNameListSecondaryPanelDiagnostics(val, val2, entryCount, 0, 0f, "native"); return true; } int num = (entryCount - 8 + 1) / 2; if (num <= 0) { return true; } if (!TryGetNameListLocalRowSpacing(playerNameUis, out var spacing, out var source)) { if (!TryGetCachedNameListRowSpacing(instanceID, out spacing, out source)) { LogNameListDiagnostics("secondary-panel:no-spacing:" + entryCount, "name list secondary panel extension skipped: row spacing unavailable entries=" + entryCount + " panel=" + SafeLogToken(DescribeUnityObject(val))); return false; } LogNameListDiagnostics("secondary-panel:cached-spacing:" + entryCount + ":" + (int)(spacing * 10f), "name list secondary panel using cached row spacing entries=" + entryCount + " spacing=" + spacing.ToString("0.0") + " source=" + SafeLogToken(source)); } else { CacheNameListRowSpacing(instanceID, spacing, source); } float num2 = Math.Max(1f, spacing * (float)num); NameListSecondaryPanelState nameListSecondaryPanelState = s_nameListSecondaryPanelState; val2.sizeDelta = new Vector2(nameListSecondaryPanelState.SizeDelta.x, nameListSecondaryPanelState.SizeDelta.y + num2); ForceCanvasVisualRefresh(val); LogNameListSecondaryPanelDiagnostics(val, val2, entryCount, num, num2, source); return true; } private static bool TryGetNameListLocalRowSpacing(object playerNameUis, out float spacing, out string source) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_00c5: 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_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) spacing = 0f; source = "none"; if (playerNameUis == null) { return false; } int num = Math.Min(GetCollectionCount(playerNameUis), 8); int num2 = ((num >= 6) ? 4 : 0); int num3 = num2 + 2; if (num3 >= num) { return false; } if (!TryGetIndexedItem(playerNameUis, num2, out var item) || !TryGetIndexedItem(playerNameUis, num3, out var item2)) { return false; } GameObject nameListRowElementGameObject = GetNameListRowElementGameObject(item); GameObject nameListRowElementGameObject2 = GetNameListRowElementGameObject(item2); RectTransform val = (RectTransform)(((Object)(object)nameListRowElementGameObject == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); RectTransform val2 = (RectTransform)(((Object)(object)nameListRowElementGameObject2 == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return false; } Vector3 val3 = ((Transform)val2).localPosition - ((Transform)val).localPosition; if (Math.Abs(val3.y) >= 0.5f) { spacing = Math.Abs(val3.y); source = "local:" + num2 + "-" + num3; return true; } Vector2 val4 = val2.anchoredPosition - val.anchoredPosition; if (Math.Abs(val4.y) >= 0.5f) { spacing = Math.Abs(val4.y); source = "anchored:" + num2 + "-" + num3; return true; } if (TryGetNameListScreenRowSpacing(playerNameUis, num2, num3, out var spacing2) && spacing2 >= 1f) { spacing = spacing2; source = "screen:" + num2 + "-" + num3; return true; } return false; } private static void CacheNameListRowSpacing(int panelInstanceId, float spacing, string source) { if (panelInstanceId != 0 && !(spacing < 0.5f)) { s_cachedNameListRowSpacing = Math.Abs(spacing); s_cachedNameListRowSpacingSource = (string.IsNullOrWhiteSpace(source) ? "unknown" : source); s_cachedNameListRowSpacingPanelInstanceId = panelInstanceId; } } private static bool TryGetCachedNameListRowSpacing(int panelInstanceId, out float spacing, out string source) { spacing = 0f; source = "none"; if (panelInstanceId == 0 || s_cachedNameListRowSpacingPanelInstanceId != panelInstanceId || s_cachedNameListRowSpacing < 0.5f) { return false; } spacing = s_cachedNameListRowSpacing; source = "cached:" + (s_cachedNameListRowSpacingSource ?? "unknown"); return true; } private static bool TryGetAnyCachedNameListRowSpacing(out float spacing, out string source) { spacing = 0f; source = "none"; if (s_cachedNameListRowSpacing < 0.5f) { return false; } spacing = s_cachedNameListRowSpacing; source = "cached:" + (s_cachedNameListRowSpacingSource ?? "unknown"); return true; } private static void ResetNameListRowSpacingCache(int panelInstanceId = 0) { s_cachedNameListRowSpacing = 0f; s_cachedNameListRowSpacingSource = null; s_cachedNameListRowSpacingPanelInstanceId = panelInstanceId; } private static void LogNameListSecondaryPanelDiagnostics(GameObject panel, RectTransform rectTransform, int entryCount, int extraRowLevels, float extension, string status) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) if (IsVerboseLoggingEnabled() && !((Object)(object)panel == (Object)null) && !((Object)(object)rectTransform == (Object)null)) { LogNameListDiagnostics("secondary-panel:" + entryCount + ":" + extraRowLevels + ":" + (int)extension + ":" + (int)rectTransform.sizeDelta.x + ":" + (int)rectTransform.sizeDelta.y + ":" + (int)rectTransform.anchoredPosition.x + ":" + (int)rectTransform.anchoredPosition.y + ":" + SafeLogToken(status), "name list secondary panel entries=" + entryCount + " extraRowLevels=" + extraRowLevels + " extension=" + extension.ToString("0.0") + " status=" + SafeLogToken(status) + " panel=" + SafeLogToken(DescribeUnityObject(panel)) + " active=" + panel.activeSelf + " sizeDelta=" + FormatVector2(rectTransform.sizeDelta) + " anchored=" + FormatVector2(rectTransform.anchoredPosition) + " pivot=" + FormatVector2(rectTransform.pivot) + " anchors=" + FormatVector2(rectTransform.anchorMin) + "/" + FormatVector2(rectTransform.anchorMax) + " children=" + SafeLogToken(DescribeChildGameObjects(panel, 8))); } } private static void LogNameListPlacementDiagnostics(string source, object ui, object playerNameUis, IReadOnlyList entries, int slotCount, int extraSynced) { //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) if (ui != null && entries != null && entries.Count > 8) { object fieldOrProp = GetFieldOrProp(ui, "playerNameListSecondaryPanel"); GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); RectTransform val2 = (RectTransform)(((Object)(object)val == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val2 == (Object)null && (Object)(object)val != (Object)null) { val2 = val.GetComponent(); } List list = new List(); List list2 = new List(); int num = Math.Min(entries.Count, 20); for (int i = 0; i < num; i++) { TryGetNameListPlacementObjects(playerNameUis, slotCount, i, out var element, out var text, out var kind); int guiY; string text2 = FormatNameListPlacementObject(element, out guiY); int guiY2; string text3 = FormatNameListPlacementObject(text, out guiY2); string value = ((i < entries.Count) ? entries[i].Label : string.Empty); list.Add("#" + i + ":" + kind + " '" + SafeLogToken(value) + "' e{" + text2 + "} t{" + text3 + "}"); list2.Add(i + ":" + guiY + ":" + guiY2); } string text4 = (((Object)(object)val2 == (Object)null) ? "panel:none" : ("panel:" + (int)val2.sizeDelta.x + "," + (int)val2.sizeDelta.y + ":" + (int)val2.anchoredPosition.x + "," + (int)val2.anchoredPosition.y + ":" + (int)((Transform)val2).localPosition.x + "," + (int)((Transform)val2).localPosition.y)); string text5 = source + ":" + entries.Count + ":" + slotCount + ":" + extraSynced + ":" + text4 + ":" + string.Join(",", list2); DateTime utcNow = DateTime.UtcNow; if (!(utcNow < s_nextNameListPlacementDiagnosticsUtc)) { s_lastNameListPlacementDiagnosticsStatus = text5; s_nextNameListPlacementDiagnosticsUtc = utcNow.AddSeconds(IsVerboseLoggingEnabled() ? 0.25 : 0.45); MelonLogger.Msg("[NameListPlacement] source=" + SafeLogToken(source) + " entries=" + entries.Count + " slots=" + slotCount + " extraSynced=" + extraSynced + " panel=" + FormatNameListPanelPlacement(val, val2) + " rows=[" + string.Join(" | ", list) + "]"); } } } private static bool TryGetNameListPlacementObjects(object playerNameUis, int slotCount, int index, out object element, out object text, out string kind) { element = null; text = null; kind = "missing"; if (index >= slotCount) { ExtraNameListRow extraNameListRow = GetExtraNameListRow(index - slotCount); if (extraNameListRow == null) { kind = "extra-missing"; return false; } element = extraNameListRow.Element; text = extraNameListRow.Text; kind = "extra"; return true; } if (!TryGetIndexedItem(playerNameUis, index, out var item) || item == null) { kind = "native-missing"; return false; } element = GetFieldOrProp(item, "element"); text = GetFieldOrProp(item, "playerNameText"); kind = "native"; return true; } private static string FormatNameListPanelPlacement(GameObject panel, RectTransform rectTransform) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)panel == (Object)null || (Object)(object)rectTransform == (Object)null) { return "none"; } return "name='" + SafeLogToken(((Object)panel).name) + "' active=" + panel.activeSelf + " parent='" + SafeLogToken(((Object)(object)((Transform)rectTransform).parent == (Object)null) ? "none" : ((Object)((Transform)rectTransform).parent).name) + "' size=" + FormatVector2(rectTransform.sizeDelta) + " anchored=" + FormatVector2(rectTransform.anchoredPosition) + " local=" + FormatVector3(((Transform)rectTransform).localPosition) + " pivot=" + FormatVector2(rectTransform.pivot) + " anchors=" + FormatVector2(rectTransform.anchorMin) + "/" + FormatVector2(rectTransform.anchorMax); } private static string FormatNameListPlacementObject(object obj, out int guiY) { //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) guiY = int.MinValue; if (obj == null) { return "null"; } GameObject val = TryGetGameObjectForDiagnostics(obj); RectTransform val2 = (RectTransform)((obj is RectTransform) ? obj : null); if ((Object)(object)val2 == (Object)null && (Object)(object)val != (Object)null) { Transform transform = val.transform; val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); } if ((Object)(object)val2 == (Object)null) { Component val3 = (Component)((obj is Component) ? obj : null); if (val3 != null) { Transform transform2 = val3.transform; val2 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); } } string text = "none"; if (TryGetObjectGuiRect(obj, out var x, out var y, out var width, out var height, out var source)) { guiY = (int)y; text = SafeLogToken(source) + ":" + FormatGuiRect(x, y, width, height); } string value = (((Object)(object)val == (Object)null) ? DescribeUnityObject(obj) : ((Object)val).name); string text2 = (((Object)(object)val == (Object)null) ? "?" : val.activeSelf.ToString()); if ((Object)(object)val2 == (Object)null) { return "name='" + SafeLogToken(value) + "' active=" + text2 + " rect=none gui=" + text; } return "name='" + SafeLogToken(value) + "' active=" + text2 + " parent='" + SafeLogToken(((Object)(object)((Transform)val2).parent == (Object)null) ? "none" : ((Object)((Transform)val2).parent).name) + "' anchored=" + FormatVector2(val2.anchoredPosition) + " local=" + FormatVector3(((Transform)val2).localPosition) + " size=" + FormatVector2(val2.sizeDelta) + " gui=" + text; } private static int SyncExtraNameListRows(object ui, object playerNameUis, IReadOnlyList entries, int startIndex) { //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) if (ui == null || playerNameUis == null || entries == null || startIndex >= entries.Count) { return HideExtraNameListRows(); } if (s_extraNameListUi == null) { s_extraNameListUi = ui; } else if (s_extraNameListUi != ui) { ClearExtraNameListRows(); s_extraNameListUi = ui; } int num = entries.Count - startIndex; int originalSlotCount = Math.Min(GetCollectionCount(playerNameUis), 8); while (s_extraNameListRows.Count < num) { int index = startIndex + s_extraNameListRows.Count; if (!TryCreateExtraNameListRow(playerNameUis, originalSlotCount, index, out var row, out var reason)) { LogNameListDiagnostics("extra-create-failed:" + SafeLogToken(reason) + ":" + startIndex + ":" + num, "name list extra native clone failed index=" + index + " reason=" + SafeLogToken(reason)); break; } s_extraNameListRows.Add(row); } int num2 = 0; for (int i = 0; i < s_extraNameListRows.Count; i++) { ExtraNameListRow extraNameListRow = s_extraNameListRows[i]; if (extraNameListRow != null && !((Object)(object)extraNameListRow.Element == (Object)null)) { if (i >= num) { extraNameListRow.Element.SetActive(false); continue; } PathEntry pathEntry = entries[startIndex + i]; bool flag = IsPathVisible(pathEntry.Key); extraNameListRow.Element.SetActive(true); RepositionExtraNameListRow(playerNameUis, originalSlotCount, startIndex + i, extraNameListRow); SetTextValue(extraNameListRow.Text, pathEntry.Label ?? string.Empty); SetTextColorValue(extraNameListRow.Text, flag ? new Color(0.88f, 0.88f, 0.86f, 1f) : new Color(0.42f, 0.42f, 0.42f, 1f)); SetNameListElementMarkerColor(extraNameListRow.Element, pathEntry.Color, flag); ForceNameListRowVisualRefresh(extraNameListRow); PrimeNameListRowGeometry(extraNameListRow); num2++; } } return Math.Min(num2, num); } private static bool TryCreateExtraNameListRow(object playerNameUis, int originalSlotCount, int index, out ExtraNameListRow row, out string reason) { row = null; if (!TryCreateNameListClone(playerNameUis, originalSlotCount, index, out var elementClone, out var text, out reason)) { return false; } row = new ExtraNameListRow(elementClone, text); return true; } private static void RepositionExtraNameListRow(object playerNameUis, int originalSlotCount, int index, ExtraNameListRow row) { if (playerNameUis != null && row != null && originalSlotCount > 0) { RectTransform val = (RectTransform)(((Object)(object)row.Element == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val != (Object)null) { PositionExpandedNameListRow(playerNameUis, originalSlotCount, index, val); } } } private static int HideExtraNameListRows() { foreach (ExtraNameListRow s_extraNameListRow in s_extraNameListRows) { try { if ((Object)(object)s_extraNameListRow?.Element != (Object)null) { s_extraNameListRow.Element.SetActive(false); } } catch { } } return 0; } private static void ClearExtraNameListRows() { foreach (ExtraNameListRow s_extraNameListRow in s_extraNameListRows) { try { if ((Object)(object)s_extraNameListRow?.Element != (Object)null) { Object.Destroy((Object)(object)s_extraNameListRow.Element); } } catch { } } s_extraNameListRows.Clear(); s_extraNameListUi = null; s_nameListSecondaryPanelState = null; ResetNameListRowSpacingCache(); ResetNameListRowTransformStates(); } private static void ResetNameListRowTransformStates() { s_nameListRowTransformStates.Clear(); } private static int GetExtraNameListRowCount(object ui) { if (ui == null || s_extraNameListUi != ui) { return 0; } int num = 0; foreach (ExtraNameListRow s_extraNameListRow in s_extraNameListRows) { try { if ((Object)(object)s_extraNameListRow?.Element != (Object)null && s_extraNameListRow.Element.activeSelf) { num++; } } catch { } } return num; } private static ExtraNameListRow GetExtraNameListRow(int extraIndex) { if (s_extraNameListUi != s_lastUiInstance || extraIndex < 0 || extraIndex >= s_extraNameListRows.Count) { return null; } return s_extraNameListRows[extraIndex]; } private static bool TryCreateNameListClone(object playerNameUis, int originalSlotCount, int index, out GameObject elementClone, out object text, out string reason) { elementClone = null; text = null; reason = "none"; if (playerNameUis == null || originalSlotCount <= 0) { reason = "bad-clone-args"; return false; } int nameListTemplateIndex = GetNameListTemplateIndex(index, originalSlotCount); if (!TryGetIndexedItem(playerNameUis, nameListTemplateIndex, out var item) || item == null) { reason = "template-row-missing:index=" + nameListTemplateIndex; return false; } object fieldOrProp = GetFieldOrProp(item, "element"); GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); if ((Object)(object)val == (Object)null) { reason = "template-element-missing:index=" + nameListTemplateIndex + ":row=" + DescribeObjectType(item); return false; } try { elementClone = Object.Instantiate(val); } catch (Exception ex) { reason = "instantiate-failed:" + ex.GetType().Name + ":" + ex.Message; return false; } if ((Object)(object)elementClone == (Object)null) { reason = "instantiate-null:element=true"; return false; } ((Object)elementClone).name = "player_legend_element_mod_" + (index + 1).ToString("00"); Transform transform = val.transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); Transform transform2 = elementClone.transform; RectTransform val3 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null) { ((Transform)val3).SetParent(((Transform)val2).parent, false); PositionExpandedNameListRow(playerNameUis, originalSlotCount, index, val3); } else { elementClone.transform.SetParent(val.transform.parent, false); } text = FindNameListText(elementClone); if (text != null) { SetTextValue(text, string.Empty); elementClone.SetActive(true); return true; } reason = "element-text-missing:index=" + nameListTemplateIndex + ":element=" + DescribeGameObjectComponents(elementClone); Object.Destroy((Object)(object)elementClone); return false; } private static int GetNameListTemplateIndex(int index, int slotCount) { if (slotCount <= 0) { return 0; } if (slotCount >= 6 && index >= 4) { return Math.Min(slotCount - 1, 4 + Math.Abs(index % 2)); } return Math.Min(slotCount - 1, Math.Abs(index % slotCount)); } private static object FindNameListText(GameObject root) { if ((Object)(object)root == (Object)null) { return null; } try { TMP_Text component = root.GetComponent(); if ((Object)(object)component != (Object)null) { return component; } } catch { } try { Il2CppArrayBase componentsInChildren = root.GetComponentsInChildren(true); if (componentsInChildren != null && componentsInChildren.Length > 0) { return componentsInChildren[0]; } } catch { } return null; } private static void PositionExpandedNameListRow(object playerNameUis, int originalSlotCount, int index, RectTransform cloneTransform) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)cloneTransform == (Object)null || originalSlotCount <= 0) { return; } int num = Math.Abs(index % 2); bool flag = originalSlotCount >= 8 && index >= originalSlotCount; int num2 = ((originalSlotCount >= 6) ? Math.Min(originalSlotCount - 1, 4 + num) : Math.Min(originalSlotCount - 1, num)); if (!TryGetIndexedItem(playerNameUis, num2, out var item)) { return; } GameObject nameListRowElementGameObject = GetNameListRowElementGameObject(item); RectTransform val = (RectTransform)(((Object)(object)nameListRowElementGameObject == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val == (Object)null) { return; } NameListRowTransformState orCaptureNameListRowTransformState = GetOrCaptureNameListRowTransformState(val); cloneTransform.anchorMin = orCaptureNameListRowTransformState.AnchorMin; cloneTransform.anchorMax = orCaptureNameListRowTransformState.AnchorMax; cloneTransform.pivot = orCaptureNameListRowTransformState.Pivot; cloneTransform.sizeDelta = orCaptureNameListRowTransformState.SizeDelta; ((Transform)cloneTransform).localScale = orCaptureNameListRowTransformState.LocalScale; ((Transform)cloneTransform).localRotation = orCaptureNameListRowTransformState.LocalRotation; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0f, 0f); Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(0f, 0f, 0f); bool flag2 = false; bool flag3 = false; int num3 = num2 + 2; if (num3 < originalSlotCount && TryGetIndexedItem(playerNameUis, num3, out var item2)) { GameObject nameListRowElementGameObject2 = GetNameListRowElementGameObject(item2); RectTransform val4 = (RectTransform)(((Object)(object)nameListRowElementGameObject2 == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val4 != (Object)null) { NameListRowTransformState orCaptureNameListRowTransformState2 = GetOrCaptureNameListRowTransformState(val4); val2 = orCaptureNameListRowTransformState2.AnchoredPosition - orCaptureNameListRowTransformState.AnchoredPosition; flag2 = IsUsefulNameListDelta(val2); val3 = orCaptureNameListRowTransformState2.LocalPosition - orCaptureNameListRowTransformState.LocalPosition; flag3 = IsUsefulNameListDelta(val3); } } float spacing = 0f; if (!flag2 && !flag3) { if (TryGetAnyCachedNameListRowSpacing(out spacing, out var source)) { ((Vector3)(ref val3))..ctor(0f, 0f - Math.Max(1f, spacing), 0f); flag3 = true; LogNameListDiagnostics("extra-row:cached-spacing:" + SafeLogToken(source) + ":" + (int)(spacing * 10f), "name list extra row using cached spacing source=" + SafeLogToken(source) + " spacing=" + spacing.ToString("0.0")); } else if (TryGetNameListScreenRowSpacing(playerNameUis, num2, num3, out spacing)) { spacing = Math.Max(1f, spacing); ((Vector2)(ref val2))..ctor(0f, 0f - spacing); flag2 = true; } else { spacing = 40f; ((Vector2)(ref val2))..ctor(0f, 0f - spacing); flag2 = true; } } int num4 = (flag ? (1 + Math.Max(0, (index - originalSlotCount) / 2)) : ((originalSlotCount >= 6 && index >= 4) ? Math.Max(0, (index - 4) / 2) : Math.Max(0, index / 2))); int num5 = ((originalSlotCount < 8 || index < 4) ? num4 : (flag ? (1 - num4) : (1 + num4))); if (flag3) { ((Transform)cloneTransform).localPosition = orCaptureNameListRowTransformState.LocalPosition + val3 * (float)num5; } else if (flag2) { cloneTransform.anchoredPosition = orCaptureNameListRowTransformState.AnchoredPosition + val2 * (float)num5; } ((Transform)cloneTransform).SetAsLastSibling(); } private static NameListRowTransformState GetOrCaptureNameListRowTransformState(RectTransform rectTransform) { if ((Object)(object)rectTransform == (Object)null) { return null; } int instanceID = ((Object)rectTransform).GetInstanceID(); if (!s_nameListRowTransformStates.TryGetValue(instanceID, out var value)) { value = new NameListRowTransformState(rectTransform); s_nameListRowTransformStates[instanceID] = value; } return value; } private static bool TryGetNameListRowRectTransform(object obj, out RectTransform rectTransform) { rectTransform = (RectTransform)((obj is RectTransform) ? obj : null); if ((Object)(object)rectTransform != (Object)null) { return true; } GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null) { Transform transform = val.transform; rectTransform = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)rectTransform == (Object)null) { rectTransform = val.GetComponent(); } return (Object)(object)rectTransform != (Object)null; } Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { Transform transform2 = val2.transform; rectTransform = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); return (Object)(object)rectTransform != (Object)null; } GameObject val3 = TryGetGameObjectForDiagnostics(obj); if ((Object)(object)val3 == (Object)null) { return false; } Transform transform3 = val3.transform; rectTransform = (RectTransform)(object)((transform3 is RectTransform) ? transform3 : null); if ((Object)(object)rectTransform == (Object)null) { rectTransform = val3.GetComponent(); } return (Object)(object)rectTransform != (Object)null; } private static bool TryGetNameListScreenRowSpacing(object playerNameUis, int baseIndex, int nextRowIndex, out float spacing) { spacing = 0f; if (playerNameUis == null || nextRowIndex < 0) { return false; } if (!TryGetIndexedItem(playerNameUis, baseIndex, out var item) || !TryGetIndexedItem(playerNameUis, nextRowIndex, out var item2)) { return false; } GameObject nameListRowElementGameObject = GetNameListRowElementGameObject(item); GameObject nameListRowElementGameObject2 = GetNameListRowElementGameObject(item2); if ((Object)(object)nameListRowElementGameObject == (Object)null || (Object)(object)nameListRowElementGameObject2 == (Object)null) { return false; } if (!TryGetObjectGuiRect(nameListRowElementGameObject, out var x, out var y, out var width, out var height, out var source) || !TryGetObjectGuiRect(nameListRowElementGameObject2, out width, out var y2, out x, out var height2, out source)) { return false; } float num = Math.Abs(y2 - y); if (num >= 1f) { spacing = num; return true; } spacing = Math.Max(height, height2); return spacing >= 1f; } private static bool IsUsefulNameListDelta(Vector2 delta) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!(Math.Abs(delta.x) >= 0.5f)) { return Math.Abs(delta.y) >= 0.5f; } return true; } private static bool IsUsefulNameListDelta(Vector3 delta) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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 (!(Math.Abs(delta.x) >= 0.5f) && !(Math.Abs(delta.y) >= 0.5f)) { return Math.Abs(delta.z) >= 0.5f; } return true; } private static GameObject GetNameListRowElementGameObject(object row) { if (row == null) { return null; } object fieldOrProp = GetFieldOrProp(row, "element"); GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); if (val != null) { return val; } return TryGetGameObjectForDiagnostics(fieldOrProp); } private static bool TryGetGameNameListTextGuiRect(int index, out float x, out float y, out float width, out float height) { x = 0f; y = 0f; width = 0f; height = 0f; object fieldOrProp = GetFieldOrProp(s_lastUiInstance, "playerNameUIs"); int collectionCount = GetCollectionCount(fieldOrProp); if (index >= collectionCount) { ExtraNameListRow extraNameListRow = GetExtraNameListRow(index - collectionCount); if (extraNameListRow != null) { return TryGetTextGuiRect(extraNameListRow.Text, out x, out y, out width, out height); } return false; } if (!TryGetIndexedItem(fieldOrProp, index, out var item) || item == null) { return false; } return TryGetTextGuiRect(GetFieldOrProp(item, "playerNameText"), out x, out y, out width, out height); } private static void AddNameListTextDiagnostic(List rows, int index, string label, string textSource) { if (rows != null && rows.Count < 8) { rows.Add("#" + index + " label='" + SafeLogToken(label) + "' source=" + SafeLogToken(textSource)); } } private static string FormatNameListTextDiagnostics(List rows) { if (rows != null && rows.Count != 0) { return " textDetails=[" + string.Join(" | ", rows) + "]"; } return string.Empty; } private static void AddNameListClickDiagnostic(List rows, int index, string label, float x, float y, float width, float height, bool hit, string source) { if (rows != null && rows.Count < 8) { rows.Add("#" + index + " label='" + SafeLogToken(label) + "' rect=" + (int)x + "," + (int)y + "," + (int)width + "x" + (int)height + " hit=" + hit + " source=" + SafeLogToken(source)); } } private static string FormatNameListClickDiagnostics(List rows) { if (rows != null && rows.Count != 0) { return " details=[" + string.Join(" | ", rows) + "]"; } return string.Empty; } private static void MaybeLogNameListUiStructure(object ui, int slotCount, int entryCount) { if (!IsVerboseLoggingEnabled() || ui == null || slotCount <= 0) { return; } DateTime utcNow = DateTime.UtcNow; string text = slotCount + ":" + entryCount + ":" + DescribeObjectType(ui); if (text == s_lastNameListStructureDiagnosticsStatus && utcNow < s_nextNameListStructureDiagnosticsUtc) { return; } s_lastNameListStructureDiagnosticsStatus = text; s_nextNameListStructureDiagnosticsUtc = utcNow.AddSeconds(12.0); object fieldOrProp = GetFieldOrProp(ui, "playerNameUIs"); List list = new List(); int num = Math.Min(slotCount, 5); int num2 = 0; int num3 = 0; for (int i = 0; i < num; i++) { if (!TryGetIndexedItem(fieldOrProp, i, out var item) || item == null) { list.Add("#" + i + " row=null"); continue; } object fieldOrProp2 = GetFieldOrProp(item, "element"); object fieldOrProp3 = GetFieldOrProp(item, "playerNameText"); bool hasRect; string text2 = DescribeGuiRect(fieldOrProp3, out hasRect); bool hasRect2; string text3 = DescribeGuiRect(fieldOrProp2, out hasRect2); if (hasRect) { num2++; } if (hasRect2) { num3++; } list.Add("#" + i + " rowType=" + SafeLogToken(DescribeObjectType(item)) + " element=" + SafeLogToken(DescribeUnityObject(fieldOrProp2)) + " elementRect=" + text3 + " elementComponents=" + SafeLogToken(DescribeGameObjectComponents(fieldOrProp2)) + " text=" + SafeLogToken(DescribeUnityObject(fieldOrProp3)) + " textRect=" + text2 + " textComponents=" + SafeLogToken(DescribeGameObjectComponents(fieldOrProp3))); } MelonLogger.Msg("Name-list structure: ui=" + SafeLogToken(DescribeUnityObject(ui)) + " slots=" + slotCount + " entries=" + entryCount + " checked=" + num + " textRects=" + num2 + " elementRects=" + num3 + " rows=[" + string.Join(" | ", list) + "]"); } private static string DescribeGuiRect(object obj, out bool hasRect) { hasRect = TryGetObjectGuiRect(obj, out var x, out var y, out var width, out var height, out var source); if (!hasRect) { return "none"; } return SafeLogToken(source) + ":" + (int)x + "," + (int)y + "," + (int)width + "x" + (int)height; } private static string DescribeUnityObject(object obj) { if (obj == null) { return "null"; } string text = DescribeObjectType(obj); string text2 = string.Empty; try { Object val = (Object)((obj is Object) ? obj : null); if (val != null) { text2 = val.name; } } catch { } if (string.IsNullOrWhiteSpace(text2)) { text2 = GetFieldOrProp(obj, "name") as string; } return text + (string.IsNullOrWhiteSpace(text2) ? string.Empty : ("('" + text2 + "')")); } private static string DescribeObjectType(object obj) { object obj2; if (obj != null) { obj2 = obj.GetType().FullName; if (obj2 == null) { return obj.GetType().Name; } } else { obj2 = "null"; } return (string)obj2; } private static string DescribeGameObjectComponents(object obj) { GameObject val = TryGetGameObjectForDiagnostics(obj); if ((Object)(object)val == (Object)null) { return "gameObject:none"; } try { Il2CppArrayBase components = val.GetComponents(); if (components == null) { return "gameObject:" + ((Object)val).name + " components:none"; } List list = new List(); foreach (Component item in components) { if (!((Object)(object)item == (Object)null)) { list.Add(((object)item).GetType().Name); if (list.Count >= 8) { break; } } } return "gameObject:" + ((Object)val).name + " components=" + string.Join(",", list); } catch (Exception ex) { return "gameObject:" + ((Object)val).name + " components:error:" + ex.GetType().Name; } } private static GameObject TryGetGameObjectForDiagnostics(object obj) { try { GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null) { return val; } Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { return val2.gameObject; } object fieldOrProp = GetFieldOrProp(obj, "gameObject"); return (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); } catch { return null; } } private static string GetNameListTextDiagnostic(object text) { if (text == null) { return "text:none"; } float x; float y; float width; float height; string source; return "text:" + text.GetType().Name + " rect=" + (TryGetObjectGuiRect(text, out x, out y, out width, out height, out source) ? (SafeLogToken(source) + ":" + (int)x + "," + (int)y + "," + (int)width + "x" + (int)height) : "none"); } private static bool TryGetTextGuiRect(object textObject, out float x, out float y, out float width, out float height) { string source; return TryGetObjectGuiRect(textObject, out x, out y, out width, out height, out source); } private static bool TryGetObjectGuiRect(object element, out float x, out float y, out float width, out float height, out string source) { x = 0f; y = 0f; width = 0f; height = 0f; source = "none"; if (element == null) { return false; } try { RectTransform val = (RectTransform)((element is RectTransform) ? element : null); if (val != null && TryGetRectTransformGuiRect(val, out x, out y, out width, out height)) { source = "rect-transform"; return true; } object fieldOrProp = GetFieldOrProp(element, "rectTransform"); RectTransform val2 = (RectTransform)((fieldOrProp is RectTransform) ? fieldOrProp : null); if (val2 != null && TryGetRectTransformGuiRect(val2, out x, out y, out width, out height)) { source = "rectTransform-prop"; return true; } object fieldOrProp2 = GetFieldOrProp(element, "transform"); RectTransform val3 = (RectTransform)((fieldOrProp2 is RectTransform) ? fieldOrProp2 : null); if (val3 != null && TryGetRectTransformGuiRect(val3, out x, out y, out width, out height)) { source = "transform-rect"; return true; } Component val4 = (Component)((element is Component) ? element : null); if (val4 != null) { Transform transform = val4.transform; RectTransform val5 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val5 != null && TryGetRectTransformGuiRect(val5, out x, out y, out width, out height)) { source = "component-transform"; return true; } RectTransform component = val4.GetComponent(); if ((Object)(object)component != (Object)null && TryGetRectTransformGuiRect(component, out x, out y, out width, out height)) { source = "component-rect-component"; return true; } RectTransform val6 = (((Object)(object)val4.gameObject == (Object)null) ? null : val4.gameObject.GetComponent()); if ((Object)(object)val6 != (Object)null && TryGetRectTransformGuiRect(val6, out x, out y, out width, out height)) { source = "component-gameobject-rect"; return true; } } GameObject val7 = (GameObject)(((element is GameObject) ? element : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); RectTransform val8 = (((Object)(object)val7 == (Object)null) ? null : val7.GetComponent()); if ((Object)(object)val8 != (Object)null && TryGetRectTransformGuiRect(val8, out x, out y, out width, out height)) { source = "gameobject-rect"; return true; } return false; } catch { return false; } } private static bool TryGetRectTransformGuiRect(RectTransform rectTransform, out float x, out float y, out float width, out float height) { x = 0f; y = 0f; width = 0f; height = 0f; if ((Object)(object)rectTransform == (Object)null) { return false; } return TryGetRectTransformWorldCornersGuiRect(rectTransform, out x, out y, out width, out height); } private static bool TryGetRectTransformWorldCornersGuiRect(RectTransform rectTransform, out float x, out float y, out float width, out float height) { x = 0f; y = 0f; width = 0f; height = 0f; if ((Object)(object)rectTransform == (Object)null) { return false; } try { Il2CppStructArray val = new Il2CppStructArray(4L); rectTransform.GetWorldCorners(val); if (TryBuildGuiRectFromRectTransformUtility(rectTransform, val, out x, out y, out width, out height)) { LogRectTransformDiagnostics(rectTransform, "recttransform-utility:" + FormatGuiRect(x, y, width, height), val); return true; } LogRectTransformDiagnostics(rectTransform, "world-corners-unusable", val); return false; } catch (Exception ex) { LogRectTransformDiagnostics(rectTransform, "world-corners-exception:" + ex.GetType().Name, null); return false; } } private static bool TryBuildGuiRectFromRectTransformUtility(RectTransform rectTransform, Il2CppStructArray corners, out float x, out float y, out float width, out float height) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) x = 0f; y = 0f; width = 0f; height = 0f; if ((Object)(object)rectTransform == (Object)null || corners == null || ((Il2CppArrayBase)(object)corners).Length <= 0) { return false; } try { Canvas componentInParent = ((Component)rectTransform).GetComponentInParent(); Camera val = null; if ((Object)(object)componentInParent != (Object)null && (int)componentInParent.renderMode != 0) { val = (((Object)(object)componentInParent.worldCamera != (Object)null) ? componentInParent.worldCamera : Camera.main); } float num = float.MaxValue; float num2 = float.MaxValue; float num3 = float.MinValue; float num4 = float.MinValue; for (int i = 0; i < ((Il2CppArrayBase)(object)corners).Length; i++) { Vector2 val2 = RectTransformUtility.WorldToScreenPoint(val, ((Il2CppArrayBase)(object)corners)[i]); num = Math.Min(num, val2.x); num2 = Math.Min(num2, val2.y); num3 = Math.Max(num3, val2.x); num4 = Math.Max(num4, val2.y); } if (num3 <= num || num4 <= num2) { return false; } x = num; y = (float)ScreenHeight() - num4; width = num3 - num; height = num4 - num2; return IsPlausibleGuiRect(x, y, width, height); } catch { return false; } } private static bool IsPlausibleGuiRect(float x, float y, float width, float height) { if (width <= 1f || height <= 1f) { return false; } int num = ScreenWidth(); int num2 = ScreenHeight(); if (x + width >= -32f && y + height >= -32f && x <= (float)num + 32f) { return y <= (float)num2 + 32f; } return false; } private static string FormatGuiRect(float x, float y, float width, float height) { return (int)x + "," + (int)y + "," + (int)width + "x" + (int)height; } private static string FormatVector2(Vector2 value) { return value.x.ToString("0.0") + "," + value.y.ToString("0.0"); } private static string FormatVector3(Vector3 value) { return value.x.ToString("0.0") + "," + value.y.ToString("0.0") + "," + value.z.ToString("0.0"); } private static string DescribeChildGameObjects(GameObject gameObject, int limit) { if ((Object)(object)gameObject == (Object)null || limit <= 0) { return "none"; } try { Transform transform = gameObject.transform; if ((Object)(object)transform == (Object)null || transform.childCount <= 0) { return "none"; } List list = new List(); for (int i = 0; i < transform.childCount; i++) { if (list.Count >= limit) { break; } Transform child = transform.GetChild(i); if (!((Object)(object)child == (Object)null)) { list.Add(((Object)child).name + ":" + DescribeGameObjectComponents(((Component)child).gameObject)); } } return (list.Count == 0) ? "none" : string.Join("|", list); } catch (Exception ex) { return "error:" + ex.GetType().Name; } } private static void LogRectTransformDiagnostics(RectTransform rectTransform, string status, Il2CppStructArray corners) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if (!IsVerboseLoggingEnabled()) { return; } DateTime utcNow = DateTime.UtcNow; string key = status + ":" + DescribeUnityObject(rectTransform); if (s_nextRectTransformDiagnosticsBySignature.TryGetValue(key, out var value) && utcNow < value) { return; } s_nextRectTransformDiagnosticsUtc = utcNow.AddSeconds(6.0); s_nextRectTransformDiagnosticsBySignature[key] = s_nextRectTransformDiagnosticsUtc; string text = "corners=none"; if (corners != null) { List list = new List(); for (int i = 0; i < ((Il2CppArrayBase)(object)corners).Length && i < 4; i++) { Vector3 val = ((Il2CppArrayBase)(object)corners)[i]; list.Add((int)val.x + "," + (int)val.y + "," + (int)val.z); } text = "corners=[" + string.Join(";", list) + "]"; } MelonLogger.Msg("Name-list RectTransform diagnostics: status=" + SafeLogToken(status) + " object=" + SafeLogToken(DescribeUnityObject(rectTransform)) + " " + text); } private static bool SetTextValue(object textObject, string text) { if (textObject == null) { return false; } if (SetFieldOrProp(textObject, "text", text ?? string.Empty) || SetFieldOrProp(textObject, "Text", text ?? string.Empty)) { ForceTextVisualRefresh(textObject); return true; } try { MethodInfo methodInfo = textObject.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "SetText" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(string)); if (methodInfo == null) { return false; } methodInfo.Invoke(textObject, new object[1] { text ?? string.Empty }); ForceTextVisualRefresh(textObject); return true; } catch { return false; } } private static bool SetTextColorValue(object textObject, Color color) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (textObject == null || !SetFieldOrProp(textObject, "color", color)) { return false; } ForceTextVisualRefresh(textObject); return true; } private static void ForceNameListRowVisualRefresh(ExtraNameListRow row) { if (row != null) { ForceTextVisualRefresh(row.Text); ForceCanvasVisualRefresh(row.Element); } } private static void PrimeNameListRowGeometry(ExtraNameListRow row) { if (row != null) { PrimeNameListRowGeometry(row.Element, row.Text); } } private static void PrimeNameListRowGeometry(object element, object text) { TryGetObjectGuiRect(element, out var x, out var y, out var width, out var height, out var _); TryGetTextGuiRect(text, out height, out width, out y, out x); } private static void ForceTextVisualRefresh(object textObject) { if (textObject != null) { InvokeNoArg(textObject, "SetVerticesDirty"); InvokeNoArg(textObject, "SetLayoutDirty"); InvokeNoArg(textObject, "SetMaterialDirty"); InvokeForceMeshUpdate(textObject); } } private static void InvokeForceMeshUpdate(object textObject) { if (textObject == null) { return; } try { MethodInfo[] array = (from m in textObject.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "ForceMeshUpdate" orderby m.GetParameters().Length select m).ToArray(); foreach (MethodInfo methodInfo in array) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 0) { methodInfo.Invoke(textObject, Array.Empty()); break; } if (parameters.Length == 1 && parameters[0].ParameterType == typeof(bool)) { methodInfo.Invoke(textObject, new object[1] { true }); break; } if (parameters.Length == 2 && parameters[0].ParameterType == typeof(bool) && parameters[1].ParameterType == typeof(bool)) { methodInfo.Invoke(textObject, new object[2] { true, true }); break; } } } catch { } } private static void ForceCanvasVisualRefresh(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return; } try { Canvas.ForceUpdateCanvases(); } catch { } } private static bool SetNameListElementMarkerColor(object element, TrailColor color, bool visible) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) try { GameObject val = (GameObject)(((element is GameObject) ? element : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); if ((Object)(object)val == (Object)null) { return false; } Color color2 = (visible ? new Color((float)(int)color.R / 255f, (float)(int)color.G / 255f, (float)(int)color.B / 255f, 1f) : new Color(0.14f, 0.14f, 0.14f, 0.96f)); bool result = false; Il2CppArrayBase componentsInChildren = val.GetComponentsInChildren(true); if (componentsInChildren != null) { foreach (Image item in componentsInChildren) { if (!((Object)(object)item == (Object)null)) { ((Graphic)item).color = color2; result = true; } } } Il2CppArrayBase componentsInChildren2 = val.GetComponentsInChildren(true); if (componentsInChildren2 != null) { foreach (RawImage item2 in componentsInChildren2) { if (!((Object)(object)item2 == (Object)null)) { ((Graphic)item2).color = color2; result = true; } } } return result; } catch { return false; } } private static bool SetGameObjectActive(object obj, bool active) { if (obj == null) { return false; } try { GameObject val = (GameObject)(((obj is GameObject) ? obj : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); if ((Object)(object)val != (Object)null) { val.SetActive(active); return true; } MethodInfo method = obj.GetType().GetMethod("SetActive", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null); if (method == null) { return false; } method.Invoke(obj, new object[1] { active }); return true; } catch { return false; } } private static bool EnsureUnityReflection() { if (s_unityReflectionReady) { return true; } try { s_rectType = FindType("UnityEngine.Rect"); s_colorType = FindType("UnityEngine.Color"); s_guiType = FindType("UnityEngine.GUI"); s_texture2DType = FindType("UnityEngine.Texture2D"); s_textureType = FindType("UnityEngine.Texture"); s_screenType = FindType("UnityEngine.Screen"); s_inputType = FindType("UnityEngine.Input"); if (s_rectType == null || s_colorType == null || s_guiType == null || s_texture2DType == null || s_textureType == null) { return LogUnityReflectionMissing(); } s_rectCtor = s_rectType.GetConstructor(new Type[4] { typeof(float), typeof(float), typeof(float), typeof(float) }); s_colorCtor = s_colorType.GetConstructor(new Type[4] { typeof(float), typeof(float), typeof(float), typeof(float) }); s_guiColorProperty = s_guiType.GetProperty("color", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_guiSkinProperty = s_guiType.GetProperty("skin", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_whiteTextureProperty = s_texture2DType.GetProperty("whiteTexture", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_screenWidthProperty = s_screenType?.GetProperty("width", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_screenHeightProperty = s_screenType?.GetProperty("height", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_inputMousePositionProperty = s_inputType?.GetProperty("mousePosition", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_guiDrawTexture = s_guiType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "DrawTexture" && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == s_rectType && s_textureType.IsAssignableFrom(m.GetParameters()[1].ParameterType)); s_guiLabel = s_guiType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Label" && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == s_rectType && m.GetParameters()[1].ParameterType == typeof(string)); s_guiButton = s_guiType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Button" && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == s_rectType && m.GetParameters()[1].ParameterType == typeof(string)); s_guiToggle = s_guiType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Toggle" && m.GetParameters().Length == 3 && m.GetParameters()[0].ParameterType == s_rectType && m.GetParameters()[1].ParameterType == typeof(bool) && m.GetParameters()[2].ParameterType == typeof(string)); s_guiHorizontalSlider = s_guiType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "HorizontalSlider" && m.GetParameters().Length == 4 && m.GetParameters()[0].ParameterType == s_rectType && m.GetParameters()[1].ParameterType == typeof(float) && m.GetParameters()[2].ParameterType == typeof(float) && m.GetParameters()[3].ParameterType == typeof(float)); s_inputGetMouseButtonDown = s_inputType?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetMouseButtonDown" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(int)); if (s_rectCtor == null || s_colorCtor == null || s_guiDrawTexture == null || s_guiLabel == null || s_guiButton == null || s_guiToggle == null || s_guiHorizontalSlider == null) { return LogUnityReflectionMissing(); } s_whiteTexture = s_whiteTextureProperty?.GetValue(null); s_unityReflectionReady = s_whiteTexture != null; return s_unityReflectionReady || LogUnityReflectionMissing(); } catch (Exception ex) { if (DateTime.UtcNow >= s_nextGuiWarningUtc) { s_nextGuiWarningUtc = DateTime.UtcNow.AddSeconds(10.0); MelonLogger.Warning("Unity IMGUI reflection init failed: " + ex.GetType().Name + " " + ex.Message); } return false; } } private static bool LogUnityReflectionMissing() { if (DateTime.UtcNow >= s_nextGuiWarningUtc) { s_nextGuiWarningUtc = DateTime.UtcNow.AddSeconds(10.0); MelonLogger.Warning("Unity IMGUI reflection is not ready yet."); } return false; } private static void DrawSolid(float x, float y, float width, float height, object color) { if (s_whiteTexture != null) { SetGuiColor(color); s_guiDrawTexture.Invoke(null, new object[2] { Rect(x, y, width, height), s_whiteTexture }); SetGuiColor(Color(1f, 1f, 1f, 1f)); } } private static void Label(float x, float y, float width, float height, string text) { LabelColored(x, y, width, height, text, Color(1f, 1f, 1f, 1f)); } private static void LabelColored(float x, float y, float width, float height, string text, object color) { SetGuiColor(color); s_guiLabel.Invoke(null, new object[2] { Rect(x, y, width, height), text ?? string.Empty }); SetGuiColor(Color(1f, 1f, 1f, 1f)); } private static float UiScale() { float val = (float)ScreenWidth() / 1920f; float val2 = (float)ScreenHeight() / 1080f; float num = Math.Min(val, val2); if (num < 1f) { num = 0.2f + num * 0.8f; } return Clamp(num, 0.48f, 1.15f); } private static float NameListScale(int rowCount, int columnCount) { float num = UiScale(); if (rowCount <= 0) { return num; } float num2 = Math.Max(80f, (float)ScreenHeight() - S(126f, num)); float num3 = (float)rowCount * S(22f, num) + S(12f, num); if (num3 > num2) { num *= Clamp(num2 / num3, 0.62f, 1f); } float num4 = Math.Max(120f, (float)ScreenWidth() - S(120f, num)); float num5 = (float)columnCount * S(310f, num) + (float)Math.Max(0, columnCount - 1) * S(18f, num) + S(20f, num); if (num5 > num4) { num *= Clamp(num4 / num5, 0.58f, 1f); } return Clamp(num, 0.42f, 1.15f); } private static float S(float value, float scale) { return value * scale; } private static int ScaledFontSize(float baseSize, float scale) { return Math.Max(6, (int)Math.Round(baseSize * scale)); } private static void BeginScaledImguiFont(float scale) { try { object obj = s_guiSkinProperty?.GetValue(null); if (obj != null) { object fieldOrProp = GetFieldOrProp(obj, "button"); object fieldOrProp2 = GetFieldOrProp(obj, "label"); object fieldOrProp3 = GetFieldOrProp(obj, "toggle"); if (fieldOrProp != null && fieldOrProp2 != null && fieldOrProp3 != null) { s_savedGuiButtonFontSize = GetGuiStyleFontSize(fieldOrProp); s_savedGuiLabelFontSize = GetGuiStyleFontSize(fieldOrProp2); s_savedGuiToggleFontSize = GetGuiStyleFontSize(fieldOrProp3); SetGuiStyleFontSize(fieldOrProp, ScaledFontSize(13f, scale)); SetGuiStyleFontSize(fieldOrProp2, ScaledFontSize(13f, scale)); SetGuiStyleFontSize(fieldOrProp3, ScaledFontSize(13f, scale)); s_scaledImguiFontActive = true; } } } catch { s_scaledImguiFontActive = false; } } private static void EndScaledImguiFont() { if (!s_scaledImguiFontActive) { return; } try { object obj = s_guiSkinProperty?.GetValue(null); if (obj != null) { SetGuiStyleFontSize(GetFieldOrProp(obj, "button"), s_savedGuiButtonFontSize); SetGuiStyleFontSize(GetFieldOrProp(obj, "label"), s_savedGuiLabelFontSize); SetGuiStyleFontSize(GetFieldOrProp(obj, "toggle"), s_savedGuiToggleFontSize); } } catch { } finally { s_scaledImguiFontActive = false; } } private static int GetGuiStyleFontSize(object style) { if (!TryConvertInt(GetFieldOrProp(style, "fontSize"), out var result)) { return 0; } return result; } private static void SetGuiStyleFontSize(object style, int size) { if (style != null) { SetFieldOrProp(style, "fontSize", size); } } private static bool Button(float x, float y, float width, float height, string text) { SetGuiColor(Color(1f, 1f, 1f, 1f)); return Convert.ToBoolean(s_guiButton.Invoke(null, new object[2] { Rect(x, y, width, height), text ?? string.Empty })); } private static bool Toggle(float x, float y, float width, float height, bool value, string text) { SetGuiColor(Color(1f, 1f, 1f, 1f)); return Convert.ToBoolean(s_guiToggle.Invoke(null, new object[3] { Rect(x, y, width, height), value, text ?? string.Empty })); } private static float HorizontalSlider(float x, float y, float width, float height, float value, float left, float right) { SetGuiColor(Color(1f, 1f, 1f, 1f)); return Convert.ToSingle(s_guiHorizontalSlider.Invoke(null, new object[4] { Rect(x, y, width, height), value, left, right })); } private static void SetGuiColor(object color) { s_guiColorProperty?.SetValue(null, color); } private static object Rect(float x, float y, float width, float height) { return s_rectCtor.Invoke(new object[4] { x, y, width, height }); } private static object Color(float r, float g, float b, float a) { return s_colorCtor.Invoke(new object[4] { r, g, b, a }); } private static object Color(TrailColor color) { return Color((float)(int)color.R / 255f, (float)(int)color.G / 255f, (float)(int)color.B / 255f, (float)(int)color.A / 255f); } private static int ScreenWidth() { if (!(s_screenWidthProperty == null)) { return Convert.ToInt32(s_screenWidthProperty.GetValue(null)); } return 1920; } private static int ScreenHeight() { if (!(s_screenHeightProperty == null)) { return Convert.ToInt32(s_screenHeightProperty.GetValue(null)); } return 1080; } private static void RecordCurrentPlayers(float elapsedSeconds) { s_playerLookupBuffer.Clear(); CollectPlayerObjects(s_playerLookupBuffer); HashSet hashSet = new HashSet(); int num = 0; int num2 = 0; int num3 = 0; Dictionary counts = (IsPlayerDebugLoggingEnabled() ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null); Dictionary counts2 = (IsPlayerDebugLoggingEnabled() ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null); foreach (object item in s_playerLookupBuffer) { if (item == null) { Increment(counts, "null"); continue; } if (!IsPlayerSyncCandidate(item)) { Increment(counts, "not-player-sync"); continue; } if (!IsActiveInHierarchy(item)) { Increment(counts, "inactive"); continue; } num2++; string playerObjectKey = GetPlayerObjectKey(item); if (playerObjectKey == null) { Increment(counts, "no-key"); continue; } if (!hashSet.Add(playerObjectKey)) { Increment(counts, "duplicate-key"); continue; } if (!TryGetWorldPositionFromObject(item, out var worldX, out var worldZ)) { Increment(counts, "no-position"); continue; } num3++; CachePlayerIcon(playerObjectKey, item); string source; string playerObjectLabel = GetPlayerObjectLabel(item, out source); Increment(counts2, source); RecordPlayerTrailPoint(playerObjectKey, playerObjectLabel, worldX, worldZ, elapsedSeconds, TryGetPlayerDeadState(item, out var isDead) && isDead); num++; } int num4 = s_playerTrails.Values.Sum((MonsterTrail t) => t.Count); if (IsPlayerDebugLoggingEnabled() && (s_playerTrails.Count != s_lastLoggedPlayerTrailCount || DateTime.UtcNow >= s_nextPlayerDebugLogUtc)) { s_lastLoggedPlayerTrailCount = s_playerTrails.Count; s_nextPlayerDebugLogUtc = DateTime.UtcNow.AddSeconds(8.0); MelonLogger.Msg("Player path record: candidates=" + s_playerLookupBuffer.Count + " active=" + num2 + " positions=" + num3 + " recorded=" + num + " trails=" + s_playerTrails.Count + " points=" + num4 + " skipped=" + FormatCounts(counts) + " labelSources=" + FormatCounts(counts2) + " " + GetPlayerTrailDebugSummary()); } } private static string GetPlayerTrailDebugSummary() { if (s_playerTrails.Count == 0) { return "trailKeys=[]"; } return "trailKeys=[" + string.Join("; ", from p in s_playerTrails.OrderBy((KeyValuePair p) => p.Key).Take(6) select p.Key + ":" + (string.IsNullOrWhiteSpace(p.Value.Label) ? "Player" : p.Value.Label) + "#" + p.Value.Count) + "]"; } private static void RecordPlayerTrailPoint(string key, string label, float worldX, float worldZ, float elapsedSeconds, bool isDead) { if (!string.IsNullOrEmpty(key)) { if (!s_playerTrails.TryGetValue(key, out var value)) { value = new MonsterTrail(label, 0); s_playerTrails[key] = value; } else { value.UpdateLabelIfBetter(label); } value.Add(worldX, worldZ, elapsedSeconds, Clamp(35f, 1f, 500f)); if (value.UpdateDeadState(isDead) && IsPlayerDebugLoggingEnabled()) { MelonLogger.Msg("Player death recorded: " + (string.IsNullOrWhiteSpace(label) ? key : label) + " index=" + Math.Max(0, value.Count - 1)); } } } private static void RecordCurrentMonsters(float elapsedSeconds) { s_monsterLookupBuffer.Clear(); Dictionary dictionary = (IsMonsterChangeLoggingEnabled() ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null); Dictionary counts = (IsMonsterChangeLoggingEnabled() ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null); Dictionary counts2 = (IsMonsterChangeLoggingEnabled() ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null); CollectMonsterObjectsFromGameObjects(s_monsterLookupBuffer, dictionary); int num = 0; foreach (object item in s_monsterLookupBuffer) { if (item == null) { Increment(counts, "null"); continue; } if (!IsMonsterRecordCandidateActive(item, out var reason)) { Increment(counts, "inactive:" + reason); continue; } num++; if (!TryGetWorldPositionFromObject(item, out var worldX, out var worldZ)) { Increment(counts, "no-position"); continue; } string monsterKey = GetMonsterKey(item); if (!s_trails.TryGetValue(monsterKey, out var value)) { value = new MonsterTrail(GetMonsterLabel(item), s_nextColorIndex++); s_trails[monsterKey] = value; } else { value.UpdateLabelIfBetter(GetMonsterLabel(item)); } value.Add(worldX, worldZ, elapsedSeconds, Clamp(35f, 1f, 500f)); } if (IsMonsterChangeLoggingEnabled() && (num != s_lastLoggedActiveCount || s_trails.Count != s_lastLoggedTrailCount)) { s_lastLoggedActiveCount = num; s_lastLoggedTrailCount = s_trails.Count; string text = "lookup=" + s_monsterLookupBuffer.Count + " active=" + num + " trails=" + s_trails.Count + " points=" + s_trails.Values.Sum((MonsterTrail t) => t.Count) + " mazeScene=" + (s_currentRealMazeSceneName ?? "{unknown}") + " activeScene=" + (GetActiveSceneName() ?? "{unknown}") + " sources=" + FormatCounts(dictionary) + " skipped=" + FormatCounts(counts) + " positionSources=" + FormatCounts(counts2); MelonLogger.Msg("Recording monster paths: " + text); if (s_trails.Count == 0 || num == 0) { LogMonsterDiagnostics("record-fail:" + text, text + " examples=" + GetMonsterLookupDebugExamples()); } } } private static void ResetRecording() { s_trails.Clear(); s_playerTrails.Clear(); s_playerIcons.Clear(); s_monsterLookupBuffer.Clear(); s_playerLookupBuffer.Clear(); s_replayBuffer.Clear(); s_playerIconDrawRequests.Clear(); s_playerEntries.Clear(); s_monsterEntries.Clear(); ClearExtraNameListRows(); s_cachedGameObjectFallbackMonsters.Clear(); s_pathVisibility.Clear(); s_paused = false; s_showFullPath = true; s_scrubTime = 0f; ResetPlaybackClock(); s_lastUiInstance = null; s_lastTexture = null; s_lastPathRecorders = null; s_lastTilePositionOffset = null; s_lastPixelsPerTile = 0f; s_nextGameObjectFallbackScanUtc = DateTime.MinValue; s_fastGameObjectFallbackScansRemaining = 8; s_lastEndMapDrawUtc = DateTime.MinValue; s_nextColorIndex = 0; s_lastLoggedActiveCount = -1; s_lastLoggedTrailCount = -1; s_loggedFirstDraw = false; s_nextRecordUtc = DateTime.UtcNow.AddSeconds(1.0); s_recordingStartUtc = DateTime.MinValue; } private static float GetRecordingElapsedSeconds(DateTime now) { if (s_recordingStartUtc == DateTime.MinValue) { s_recordingStartUtc = now; } return Math.Max(0f, (float)(now - s_recordingStartUtc).TotalSeconds); } private static float GetReplayDurationSeconds() { float num = 0f; foreach (MonsterTrail value in s_playerTrails.Values) { num = Math.Max(num, value.DurationSeconds); } foreach (MonsterTrail value2 in s_trails.Values) { num = Math.Max(num, value2.DurationSeconds); } return Math.Max(0.001f, num); } private static bool IsMazeAvailable() { object staticFieldOrProp = GetStaticFieldOrProp(GetMazeGeneratorType(), "instance"); if (staticFieldOrProp != null) { return GetFieldOrProp(staticFieldOrProp, "mazeMap") != null; } return false; } private static void UpdateRealMazeSceneState(string sceneName) { if (string.Equals(sceneName, "LoadingScreen", StringComparison.OrdinalIgnoreCase)) { s_inRealMazeScene = false; s_currentRealMazeSceneName = null; } else if (IsRandomMazeSceneName(sceneName)) { s_inRealMazeScene = true; s_currentRealMazeSceneName = sceneName; } else { s_inRealMazeScene = false; s_currentRealMazeSceneName = null; } } private static bool IsRealMazeRunActive() { if (!s_inRealMazeScene) { return IsRandomMazeSceneName(GetActiveSceneName()); } return true; } private static bool IsRandomMazeSceneName(string sceneName) { if (!string.IsNullOrWhiteSpace(sceneName)) { return sceneName.StartsWith("Rand_Maze", StringComparison.OrdinalIgnoreCase); } return false; } private static string GetActiveSceneName() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; } catch { return null; } } private static bool IsObjectInCurrentRealMazeScene(object obj) { string text = s_currentRealMazeSceneName; if (string.IsNullOrWhiteSpace(text)) { string activeSceneName = GetActiveSceneName(); if (IsRandomMazeSceneName(activeSceneName)) { text = activeSceneName; } } if (string.IsNullOrWhiteSpace(text)) { return true; } string objectSceneName = GetObjectSceneName(obj); if (!string.IsNullOrWhiteSpace(objectSceneName)) { return string.Equals(objectSceneName, text, StringComparison.OrdinalIgnoreCase); } return true; } private static string GetObjectSceneName(object obj) { try { string text = GetFieldOrProp(GetFieldOrProp(GetFieldOrProp(obj, "gameObject") ?? obj, "scene"), "name") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } return GetFieldOrProp(GetFieldOrProp(GetFieldOrProp(GetFieldOrProp(obj, "transform"), "gameObject"), "scene"), "name") as string; } catch { return null; } } private static void CollectPlayerObjects(List objects) { try { object staticFieldOrProp = GetStaticFieldOrProp(GetPlayerNetworkSyncType(), "instance"); if (staticFieldOrProp != null) { objects.Add(staticFieldOrProp); } object staticFieldOrProp2 = GetStaticFieldOrProp(GetGameManagerType(), "instance"); AddPlayerCollection(objects, GetFieldOrProp(staticFieldOrProp2, "Players")); } catch (Exception ex) { LogReflectionWarning("player lookup failed: " + ex.GetType().Name + " " + ex.Message); } } private static void AddPlayerCollection(List objects, object collection) { ForEachListItem(collection, delegate(object item) { if (item != null) { objects.Add(item); } }); } private static void CollectMonsterObjectsFromGameObjects(List objects, Dictionary sourceCounts) { try { int count = objects.Count; AddCachedGameObjectFallbackMonsters(objects); int num = objects.Count - count; SetCount(sourceCounts, "scene.GameObject.cache", num); DateTime utcNow = DateTime.UtcNow; if (num > 0 || utcNow < s_nextGameObjectFallbackScanUtc) { return; } s_cachedGameObjectFallbackMonsters.Clear(); int count2 = objects.Count; int num2 = 0; int componentScanned = 0; List list = new List(); foreach (object item in FindSceneGameObjects()) { num2++; object obj = AddMonsterGameObjectCandidate(objects, item, ref componentScanned); if (obj != null) { list.Add(obj); } } SetCount(sourceCounts, "scene.GameObject.scanned", num2); SetCount(sourceCounts, "scene.GameObject.components", componentScanned); SetCount(sourceCounts, "scene.GameObject.fallback", objects.Count - count2); if (objects.Count > count2) { CacheGameObjectFallbackMonsters(list); ScheduleNextGameObjectFallbackScan(utcNow, foundObjects: true); DocumentMonsterFallbackObjects("scene.GameObject", list, num2, componentScanned); } else { ScheduleNextGameObjectFallbackScan(utcNow, foundObjects: false); } } catch (Exception ex) { LogReflectionWarning("GameObject monster fallback lookup failed: " + ex.GetType().Name + " " + ex.Message); } } private static void ScheduleNextGameObjectFallbackScan(DateTime now, bool foundObjects) { if (foundObjects) { s_fastGameObjectFallbackScansRemaining = 0; s_nextGameObjectFallbackScanUtc = now.AddSeconds(60.0); } else if (s_fastGameObjectFallbackScansRemaining > 0) { s_fastGameObjectFallbackScansRemaining--; s_nextGameObjectFallbackScanUtc = now.AddSeconds(5.0); } else { s_nextGameObjectFallbackScanUtc = now.AddSeconds(30.0); } } private static void AddCachedGameObjectFallbackMonsters(List objects) { if (s_cachedGameObjectFallbackMonsters.Count == 0) { return; } for (int num = s_cachedGameObjectFallbackMonsters.Count - 1; num >= 0; num--) { object obj = s_cachedGameObjectFallbackMonsters[num]; if (obj == null || !IsActiveInHierarchy(obj) || !LooksLikeMonsterObject(obj) || !IsObjectInCurrentRealMazeScene(obj)) { s_cachedGameObjectFallbackMonsters.RemoveAt(num); } else { AddMonsterObject(objects, obj); } } } private static void CacheGameObjectFallbackMonsters(IEnumerable accepted) { foreach (object item in accepted) { if (item != null && LooksLikeMonsterObject(item) && IsObjectInCurrentRealMazeScene(item)) { string key = GetMonsterKey(item); if (!s_cachedGameObjectFallbackMonsters.Any((object item) => string.Equals(GetMonsterKey(item), key, StringComparison.Ordinal))) { s_cachedGameObjectFallbackMonsters.Add(item); } } } } private static IEnumerable FindSceneGameObjects() { List list = new List(); try { foreach (GameObject item in Object.FindObjectsOfType()) { if ((Object)(object)item != (Object)null) { list.Add(item); } } } catch (Exception ex) { LogReflectionWarning("direct scene GameObject lookup failed: " + ex.GetType().Name + " " + ex.Message); } return list; } private static object AddMonsterGameObjectCandidate(List objects, object gameObject, ref int componentScanned) { if (gameObject == null || !IsActiveInHierarchy(gameObject) || !IsObjectInCurrentRealMazeScene(gameObject)) { return null; } if (LooksLikeMonsterObject(gameObject)) { int count = objects.Count; AddMonsterObject(objects, gameObject); if (objects.Count <= count) { return null; } return objects[objects.Count - 1]; } return null; } private static bool HasMonsterLikeAncestor(object gameObject) { try { object fieldOrProp = GetFieldOrProp(GetFieldOrProp(gameObject, "transform"), "parent"); int num = 0; while (fieldOrProp != null && num < 32) { if (TryGetKnownMonsterObjectLabel(GetFieldOrProp(fieldOrProp, "gameObject") ?? fieldOrProp, out var _)) { return true; } fieldOrProp = GetFieldOrProp(fieldOrProp, "parent"); num++; } } catch { } return false; } private static Dictionary KnownMonsterObjectNameMap() { if (s_knownMonsterObjectNameMap != null) { return s_knownMonsterObjectNameMap; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair item in ReadEmbeddedMonsterObjectNameMap()) { string text = NormalizeMonsterObjectName(item.Key); if (text.Length > 0 && !dictionary.ContainsKey(text)) { dictionary.Add(text, string.IsNullOrWhiteSpace(item.Value) ? item.Key : item.Value.Trim()); } } s_knownMonsterObjectNameMap = dictionary; LogVerbose("Loaded " + dictionary.Count + " embedded monster GameObject name mappings."); return s_knownMonsterObjectNameMap; } private static IEnumerable> ReadEmbeddedMonsterObjectNameMap() { string json = null; try { using Stream stream = typeof(MonsterPathVisualisation).Assembly.GetManifestResourceStream("MonsterPathVisualisation.MonsterNames.json"); if (stream == null) { LogReflectionWarning("Embedded monster object name resource was not found: MonsterPathVisualisation.MonsterNames.json"); yield break; } using StreamReader streamReader = new StreamReader(stream); json = streamReader.ReadToEnd(); } catch (Exception ex) { LogReflectionWarning("Failed to read embedded monster object names: " + ex.GetType().Name + " " + ex.Message); yield break; } foreach (KeyValuePair item in ParseJsonStringMap(json)) { yield return item; } } private static IEnumerable> ParseJsonStringMap(string json) { string text = null; foreach (string item in ParseJsonStringValues(json)) { if (text == null) { text = item; continue; } yield return new KeyValuePair(text, item); text = null; } } private static IEnumerable ParseJsonStringValues(string json) { if (string.IsNullOrWhiteSpace(json)) { yield break; } bool inString = false; bool escaped = false; StringBuilder current = new StringBuilder(); foreach (char c in json) { if (!inString) { if (c == '"') { inString = true; current.Clear(); } } else if (escaped) { current.Append(c switch { '"' => '"', '\\' => '\\', '/' => '/', 'b' => '\b', 'f' => '\f', 'n' => '\n', 'r' => '\r', 't' => '\t', _ => c, }); escaped = false; } else { switch (c) { case '\\': escaped = true; break; case '"': inString = false; yield return current.ToString(); break; default: current.Append(c); break; } } } } private static string NormalizeMonsterObjectName(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length + 8); char c = '\0'; foreach (char c2 in value) { if (char.IsLetterOrDigit(c2)) { if (stringBuilder.Length > 0 && char.IsLower(c) && char.IsUpper(c2)) { stringBuilder.Append(' '); } stringBuilder.Append(char.ToLowerInvariant(c2)); c = c2; } else { if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); } c = c2; } } IEnumerable values = from token in stringBuilder.ToString().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries) where !string.Equals(token, "clone", StringComparison.OrdinalIgnoreCase) select token; return string.Join(" ", values); } private static bool TryGetKnownMonsterObjectLabel(object obj, out string label) { label = null; GameObject val = (GameObject)(((obj is GameObject) ? obj : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); if ((Object)(object)val == (Object)null) { return false; } string text = NormalizeMonsterObjectName(GetObjectName(val)); if (text.Length > 0) { return KnownMonsterObjectNameMap().TryGetValue(text, out label); } return false; } private static void AddMonsterControllerCollection(List objects, object collection) { if (collection == null) { return; } object fieldOrProp = GetFieldOrProp(collection, "Values"); if (fieldOrProp != null && fieldOrProp != collection) { AddMonsterControllerCollection(objects, fieldOrProp); } ForEachListItem(collection, delegate(object item) { if (item != null) { if (LooksLikeMonsterObject(item)) { AddMonsterObject(objects, item); } else { object fieldOrProp2 = GetFieldOrProp(item, "Values"); if (fieldOrProp2 != null) { ForEachListItem(fieldOrProp2, delegate(object value) { AddMonsterControllerCollection(objects, value); }); } else if (GetCollectionCount(item) > 0) { AddMonsterControllerCollection(objects, item); } } } }); } private static void AddMonsterObject(List objects, object monster) { if (monster != null && LooksLikeMonsterObject(monster) && IsObjectInCurrentRealMazeScene(monster) && !objects.Any((object existing) => existing == monster)) { objects.Add(monster); } } private static void EnsureDrawReflection(object ui) { if (s_addPlayerDotMethod == null) { s_addPlayerDotMethod = ui.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "AddPlayerToTextureAtPosition" && m.GetParameters().Length == 5); } if (s_color32Ctor == null) { s_color32Type = FindType("UnityEngine.Color32"); s_color32Ctor = s_color32Type?.GetConstructor(new Type[4] { typeof(byte), typeof(byte), typeof(byte), typeof(byte) }); } } private static object Color32(TrailColor color) { return s_color32Ctor.Invoke(new object[4] { color.R, color.G, color.B, color.A }); } private static object PlayerColor32(object color, TrailColor fallback, bool currentOnly) { TrailColor color2 = TrailColorFromColor32(color, fallback); return Color32(currentOnly ? OpaqueTrailColor(color2) : TrailPathColor(color2)); } private static TrailColor TrailPathColor(TrailColor color) { return new TrailColor(color.R, color.G, color.B, 112); } private static TrailColor OpaqueTrailColor(TrailColor color) { return new TrailColor(color.R, color.G, color.B, byte.MaxValue); } private static float GetMaxStepWorldSpace(object ui, float tileSize, float pixelsPerTile) { if (TryConvertFloat(GetFieldOrProp(ui, "maxStepWorldSpace"), out var result) && result > 0f) { return result; } return Math.Max(0.5f, tileSize * 0.25f / Math.Max(1f, pixelsPerTile)); } private static float GetMazeTileSize() { if (TryConvertFloat(GetStaticFieldOrProp(GetMazeGeneratorType(), "TILE_SIZE"), out var result) && result > 0f) { return result; } return 10f; } private static bool TryGetWorldPositionFromObject(object obj, out float worldX, out float worldZ) { worldX = 0f; worldZ = 0f; if (obj == null) { return false; } if (TryReadVectorXZ(GetFieldOrProp(GetFieldOrProp(obj, "transform"), "position"), out worldX, out worldZ)) { return true; } return TryReadVectorXZ(GetFieldOrProp(obj, "position"), out worldX, out worldZ); } private static bool IsMonsterRecordCandidateActive(object monster) { string reason; bool result = IsMonsterRecordCandidateActive(monster, out reason); MelonLogger.Error("Monster record candidate active check: " + DescribeMonsterCandidate(monster) + " => " + result + " reason=" + reason); return result; } private static bool IsMonsterRecordCandidateActive(object monster, out string reason) { if (monster == null) { reason = "null"; return false; } if (IsActiveInHierarchy(monster)) { reason = "monster"; return true; } reason = "inactive"; return false; } private static string GetMonsterLookupDebugExamples() { if (s_monsterLookupBuffer.Count == 0) { return "[]"; } return "[" + string.Join("; ", s_monsterLookupBuffer.Where((object m) => m != null).Take(5).Select(DescribeMonsterCandidate)) + "]"; } private static string DescribeMonsterCandidate(object monster) { string reason; bool flag = IsMonsterRecordCandidateActive(monster, out reason); return DescribeMonsterObject(monster) + " label=" + GetMonsterLabel(monster) + " active=" + flag + "(" + reason + ")"; } private static void DocumentMonsterFallbackObjects(string source, List accepted, int scanned, int componentScanned) { if (!IsMonsterChangeLoggingEnabled() || accepted == null || accepted.Count == 0) { return; } try { List list = accepted.Where((object item) => item != null).Select(DescribeMonsterCandidate).Distinct(StringComparer.Ordinal) .OrderBy((string row) => row, StringComparer.Ordinal) .ToList(); if (list.Count == 0) { return; } string text = source + ":" + scanned + ":" + componentScanned + ":" + string.Join("|", list.Take(64)); DateTime utcNow = DateTime.UtcNow; if (!(text == s_lastFallbackObjectDumpSignature) || !(utcNow < s_nextFallbackObjectDumpUtc)) { s_lastFallbackObjectDumpSignature = text; s_nextFallbackObjectDumpUtc = utcNow.AddSeconds(10.0); string text2 = MonsterFallbackObjectDumpPath(); Directory.CreateDirectory(Path.GetDirectoryName(text2)); List list2 = new List(); list2.Add(""); list2.Add("## " + utcNow.ToString("O") + " source=" + source + " accepted=" + list.Count + " scanned=" + scanned + " components=" + componentScanned); List list3 = list2; list3.AddRange(list.Select((string row) => "- " + row)); File.AppendAllLines(text2, list3); LogVerbose("Documented " + list.Count + " monster fallback objects to " + text2); } } catch (Exception ex) { LogReflectionWarning("monster fallback object dump failed: " + ex.GetType().Name + " " + ex.Message); } } private static string MonsterFallbackObjectDumpPath() { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UserData", "MonsterPathVisualisationFallbackObjects.txt"); } private static string DescribeMonsterObject(object monster) { if (monster == null) { return "null"; } object monsterNetworkSyncObject = GetMonsterNetworkSyncObject(monster); object monsterControllerObject = GetMonsterControllerObject(monster); string text = monster.GetType().FullName ?? monster.GetType().Name; string text2; try { text2 = GetMonsterKey(monster); } catch { text2 = "key-error"; } return text + "#" + RuntimeHelpers.GetHashCode(monster) + " name='" + GetObjectName(monster) + "' key=" + text2 + " sync=" + (monsterNetworkSyncObject != null) + " controller=" + (monsterControllerObject != null); } private static bool IsCaseOverActive() { try { object staticFieldOrProp = GetStaticFieldOrProp(GetEndScreenUIType(), "Instance"); if (staticFieldOrProp == null) { return false; } bool result; return TryConvertBool(GetFieldOrProp(staticFieldOrProp, "IsVisible"), out result) && result; } catch { return false; } } private static bool TryReadVectorXZ(object vector, out float x, out float z) { x = 0f; z = 0f; if (vector != null && TryConvertFloat(GetFieldOrProp(vector, "x"), out x)) { return TryConvertFloat(GetFieldOrProp(vector, "z"), out z); } return false; } private static bool TryReadVectorXY(object vector, out float x, out float y) { x = 0f; y = 0f; if (vector != null && TryConvertFloat(GetFieldOrProp(vector, "x"), out x)) { return TryConvertFloat(GetFieldOrProp(vector, "y"), out y); } return false; } private static bool IsActiveInHierarchy(object obj) { bool result; return !TryConvertBool(GetFieldOrProp(GetFieldOrProp(obj, "gameObject") ?? obj, "activeInHierarchy"), out result) || result; } private static bool IsPlayerSyncCandidate(object obj) { if (obj == null || LooksLikeMonsterObject(obj)) { return false; } Type playerNetworkSyncType = GetPlayerNetworkSyncType(); if (playerNetworkSyncType != null && playerNetworkSyncType.IsInstanceOfType(obj)) { return true; } string text = obj.GetType().FullName ?? obj.GetType().Name; if (text.EndsWith("PlayerNetworkSync", StringComparison.Ordinal) || text.EndsWith(".PlayerNetworkSync", StringComparison.Ordinal)) { return true; } if (GetFieldOrProp(obj, "ClientData") == null && GetFieldOrProp(obj, "isDead") == null) { return GetFieldOrProp(obj, "NetworkisDead") != null; } return true; } private static bool LooksLikeMonsterObject(object obj) { if (obj == null) { return false; } if (TryGetKnownMonsterObjectLabel(obj, out var _)) { return !HasMonsterLikeAncestor(obj); } return false; } private static string GetObjectName(object obj) { return (GetFieldOrProp(obj, "name") as string) ?? (GetFieldOrProp(GetFieldOrProp(obj, "gameObject"), "name") as string); } private static bool TryGetPlayerDeadState(object player, out bool isDead) { isDead = false; if (!TryConvertBool(GetFieldOrProp(player, "IsDead"), out isDead) && !TryConvertBool(GetFieldOrProp(player, "NetworkisDead"), out isDead)) { return TryConvertBool(GetFieldOrProp(player, "isDead"), out isDead); } return true; } private static object GetMonsterNetworkSyncObject(object monster) { if (monster == null) { return null; } Type monsterNetworkSyncType = GetMonsterNetworkSyncType(); if (monsterNetworkSyncType != null && monsterNetworkSyncType.IsInstanceOfType(monster)) { return monster; } return GetFieldOrProp(monster, "MonsterNetworkSync") ?? GetFieldOrProp(monster, "_MonsterNetworkSync_k__BackingField"); } private static object GetMonsterControllerObject(object monster) { if (monster == null) { return null; } Type aIControllerType = GetAIControllerType(); if (aIControllerType != null && aIControllerType.IsInstanceOfType(monster)) { return monster; } object monsterNetworkSyncObject = GetMonsterNetworkSyncObject(monster); return GetFieldOrProp(monster, "AIController") ?? GetFieldOrProp(monster, "_AIController_k__BackingField") ?? GetFieldOrProp(monsterNetworkSyncObject, "AIController") ?? GetFieldOrProp(monsterNetworkSyncObject, "_AIController_k__BackingField"); } private static string GetMonsterKey(object monster) { return "object:" + RuntimeHelpers.GetHashCode(monster); } private static string GetPlayerRecorderTrailKey(object recorder, int index) { return GetPlayerObjectKey(GetFieldOrProp(recorder, "player")) ?? ("player-index:" + index); } private static string GetLocalPlayerKey() { string playerObjectKey = GetPlayerObjectKey(GetStaticFieldOrProp(GetPlayerNetworkSyncType(), "instance")); if (!string.IsNullOrEmpty(playerObjectKey)) { return playerObjectKey; } return GetPlayerObjectKey(GetFieldOrProp(GetStaticFieldOrProp(GetGameManagerType(), "instance"), "mainPlayer")); } private static string GetPlayerObjectKey(object player) { if (player == null) { return null; } if (TryConvertLong(GetFieldOrProp(player, "netId"), out var result) && result > 0) { return "net:" + result; } object playerClientData = GetPlayerClientData(player); if (TryConvertLong(GetFieldOrProp(playerClientData, "SteamID") ?? GetFieldOrProp(playerClientData, "SteamId") ?? GetFieldOrProp(playerClientData, "steamID") ?? GetFieldOrProp(playerClientData, "ID"), out var result2) && result2 > 0) { return "steam:" + result2; } if (TryConvertLong(GetFieldOrProp(player, "ID") ?? GetFieldOrProp(player, "PlayerID") ?? GetFieldOrProp(player, "playerID") ?? GetFieldOrProp(player, "ActorNumber"), out var result3) && result3 > 0) { return "player:" + result3; } return "object:" + RuntimeHelpers.GetHashCode(player); } private static string GetMonsterLabel(object monster) { if (TryGetKnownMonsterObjectLabel(monster, out var label)) { return label; } object obj = GetMonsterControllerObject(monster) ?? monster; object fieldOrProp = GetFieldOrProp(obj, "MonsterType"); if (fieldOrProp == null) { fieldOrProp = GetFieldOrProp(obj, "monsterType"); } if (fieldOrProp != null) { return fieldOrProp.ToString(); } string objectName = GetObjectName(monster); if (!string.IsNullOrWhiteSpace(objectName)) { return CleanObjectLabel(objectName); } return "Monster"; } private static string CleanObjectLabel(string name) { if (string.IsNullOrWhiteSpace(name)) { return name; } return name.Replace("(Clone)", string.Empty).Trim(); } private static string GetPlayerObjectLabel(object player) { string source; return GetPlayerObjectLabel(player, out source); } private static string GetPlayerObjectLabel(object player, out string source) { return ResolvePlayerLabel(player, null, -1, out source); } private static string GetPlayerLabel(object ui, object recorder, int index) { object fieldOrProp = GetFieldOrProp(recorder, "player"); string source; string text = ResolvePlayerLabel(fieldOrProp, ui, index, out source); if (source.StartsWith("fallback", StringComparison.OrdinalIgnoreCase)) { LogPlayerNameDiagnostics("fallback:" + index + ":" + DescribePlayerObject(fieldOrProp), "index=" + index + " source=" + source + " label='" + text + "' details=" + DescribePlayerNameSources(fieldOrProp, ui, index)); } return text; } private static string ResolvePlayerLabel(object player, object ui, int index, out string source) { string clientDataName = GetClientDataName(GetPlayerClientData(player, out var source2), out var source3); if (IsUsefulPlayerLabel(clientDataName)) { source = source2 + "." + source3; return clientDataName; } clientDataName = GetTextValue(GetFieldOrProp(player, "namePlate")); if (IsUsefulPlayerLabel(clientDataName)) { source = "namePlate.text"; return clientDataName; } clientDataName = GetEndMapPlayerName(ui, index); if (IsUsefulPlayerLabel(clientDataName)) { source = "endMap.playerNameUIs"; return clientDataName; } clientDataName = GetFieldOrProp(GetFieldOrProp(player, "gameObject") ?? player, "name") as string; if (IsUsefulPlayerLabel(clientDataName)) { source = "gameObject.name"; return clientDataName; } source = ((index >= 0) ? "fallback:index" : "fallback:generic"); if (index < 0) { return "Player"; } return "Player " + (index + 1); } private static object GetPlayerClientData(object player) { string source; return GetPlayerClientData(player, out source); } private static object GetPlayerClientData(object player, out string source) { source = "none"; if (player == null) { return null; } object fieldOrProp = GetFieldOrProp(player, "ClientData"); if (fieldOrProp != null) { source = "ClientData"; return fieldOrProp; } fieldOrProp = GetFieldOrProp(player, "NetworkclientData"); if (fieldOrProp != null) { source = "NetworkclientData"; return fieldOrProp; } fieldOrProp = GetFieldOrProp(player, "clientData"); if (fieldOrProp != null) { source = "clientData"; return fieldOrProp; } fieldOrProp = GetFieldOrProp(player, "_ClientData_k__BackingField"); if (fieldOrProp != null) { source = "_ClientData_k__BackingField"; return fieldOrProp; } fieldOrProp = GetFieldOrProp(player, "_NetworkclientData_k__BackingField"); if (fieldOrProp != null) { source = "_NetworkclientData_k__BackingField"; return fieldOrProp; } return null; } private static string GetClientDataName(object clientData) { string source; return GetClientDataName(clientData, out source); } private static string GetClientDataName(object clientData, out string source) { source = "none"; if (clientData == null) { return null; } string text = GetFieldOrProp(clientData, "Name") as string; if (!string.IsNullOrWhiteSpace(text)) { source = "Name"; return text; } text = GetFieldOrProp(clientData, "_Name_k__BackingField") as string; if (!string.IsNullOrWhiteSpace(text)) { source = "_Name_k__BackingField"; return text; } return null; } private static string GetEndMapPlayerName(object ui, int index) { if (ui == null || index < 0) { return null; } if (!TryGetIndexedItem(GetFieldOrProp(ui, "playerNameUIs"), index, out var item) || item == null) { return null; } return GetTextValue(GetFieldOrProp(item, "playerNameText")); } private static string GetTextValue(object textObject) { string text = GetFieldOrProp(textObject, "text") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } text = GetFieldOrProp(textObject, "Text") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } return null; } private static bool IsUsefulPlayerLabel(string label) { if (string.IsNullOrWhiteSpace(label)) { return false; } string text = label.Trim(); if (!string.Equals(text, "Player", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Player(Clone)", StringComparison.OrdinalIgnoreCase)) { return !text.StartsWith("PlayerNetworkSync", StringComparison.OrdinalIgnoreCase); } return false; } private static string DescribePlayerNameSources(object player, object ui, int index) { string source; string source2; string clientDataName = GetClientDataName(GetPlayerClientData(player, out source), out source2); string textValue = GetTextValue(GetFieldOrProp(player, "namePlate")); string endMapPlayerName = GetEndMapPlayerName(ui, index); string text = GetFieldOrProp(GetFieldOrProp(player, "gameObject") ?? player, "name") as string; return "player=" + DescribePlayerObject(player) + " clientData=" + source + " clientNameSource=" + source2 + " clientName='" + clientDataName + "' namePlate='" + textValue + "' endMap='" + endMapPlayerName + "' gameObject='" + text + "'"; } private static string DescribePlayerObject(object player) { if (player == null) { return "null"; } string text = player.GetType().FullName ?? player.GetType().Name; if (TryConvertLong(GetFieldOrProp(player, "netId"), out var result) && result > 0) { return text + "#net:" + result; } if (TryConvertLong(GetFieldOrProp(player, "ID"), out var result2) && result2 > 0) { return text + "#id:" + result2; } return text + "#obj:" + RuntimeHelpers.GetHashCode(player); } private static Type GetPathVisualisationUiType() { if (s_pathVisualisationUiType == null) { s_pathVisualisationUiType = FindType("ValkoGames.Labyrinthine.Cases.PathVisualisation.PathVisualisationUI"); } return s_pathVisualisationUiType; } private static Type GetMazeGeneratorType() { if (s_mazeGeneratorType == null) { s_mazeGeneratorType = FindType("MazeGenerator"); } return s_mazeGeneratorType; } private static Type GetPlayerNetworkSyncType() { if (s_playerNetworkSyncType == null) { s_playerNetworkSyncType = FindType("PlayerNetworkSync"); } return s_playerNetworkSyncType; } private static Type GetGameManagerType() { if (s_gameManagerType == null) { s_gameManagerType = FindType("GameManager"); } return s_gameManagerType; } private static Type GetEndScreenUIType() { if (s_endScreenUiType == null) { s_endScreenUiType = FindType("RandomGeneration.EndScreenUI") ?? FindType("EndScreenUI"); } return s_endScreenUiType; } private static Type GetAIControllerType() { if (s_aiControllerType == null) { s_aiControllerType = FindType("ValkoGames.Labyrinthine.Monsters.AIController"); } return s_aiControllerType; } private static Type GetMonsterNetworkSyncType() { if (s_monsterNetworkSyncType == null) { s_monsterNetworkSyncType = FindType("ValkoGames.Labyrinthine.Monsters.MonsterNetworkSync"); } return s_monsterNetworkSyncType; } private static Type FindType(string fullName) { string il2cppName = (fullName.StartsWith("Il2Cpp.", StringComparison.Ordinal) ? fullName : ("Il2Cpp." + fullName)); string shortName = (fullName.Contains(".") ? fullName.Substring(fullName.LastIndexOf('.') + 1) : fullName); Type type = Type.GetType(fullName) ?? Type.GetType(il2cppName); if (type != null) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type2 = assembly.GetType(fullName, throwOnError: false) ?? assembly.GetType(il2cppName, throwOnError: false); if (type2 != null) { return type2; } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { Type[] source; try { source = assembly2.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type t) => t != null).ToArray(); } catch { continue; } Type type3 = source.FirstOrDefault((Type t) => t.Name == shortName || t.FullName == fullName || t.FullName == il2cppName); if (type3 != null) { return type3; } } return null; } private static object GetStaticFieldOrProp(Type type, string name) { if (type == null) { return null; } try { PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(null); } } catch { } try { return type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); } catch { return null; } } private static object GetFieldOrProp(object obj, string name) { if (obj == null) { return null; } Type type = obj.GetType(); try { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(obj); } } catch { } try { return type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); } catch { return null; } } private static bool SetFieldOrProp(object obj, string name, object value) { if (obj == null) { return false; } Type type = obj.GetType(); try { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { property.SetValue(obj, value); return true; } } catch { } try { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj, value); return true; } } catch { } return false; } private static void ForEachListItem(object list, Action action) { if (list == null || action == null) { return; } if (list is Array array) { { foreach (object item in array) { action(item); } return; } } if (list is IEnumerable enumerable) { { foreach (object item2 in enumerable) { action(item2); } return; } } if (!TryConvertInt(GetFieldOrProp(list, "Count"), out var result)) { TryConvertInt(GetFieldOrProp(list, "Length"), out result); } if (result <= 0) { return; } MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return; } for (int i = 0; i < result; i++) { try { action(method.Invoke(list, new object[1] { i })); } catch { } } } private static int GetCollectionCount(object list) { if (list == null) { return 0; } if (list is Array array) { return array.Length; } if (TryConvertInt(GetFieldOrProp(list, "Count"), out var result)) { return Math.Max(0, result); } if (TryConvertInt(GetFieldOrProp(list, "Length"), out result)) { return Math.Max(0, result); } int num = 0; if (list is IEnumerable enumerable) { foreach (object item in enumerable) { _ = item; num++; } } return num; } private static bool TryGetIndexedItem(object list, object index, out object item) { item = null; if (list == null) { return false; } if (list is Array array && index is int num) { if (num < 0 || num >= array.Length) { return false; } item = array.GetValue(num); return true; } try { MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { item = method.Invoke(list, new object[1] { index }); return true; } } catch { } if (index is int num2) { int num3 = 0; if (list is IEnumerable enumerable) { foreach (object item2 in enumerable) { if (num3 == num2) { item = item2; return true; } num3++; } } } return false; } private static TrailColor TrailColorFromColor32(object color, TrailColor fallback) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //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_0026: Unknown result type (might be due to invalid IL or missing references) if (color == null) { return fallback; } if (color is Color32 val) { return new TrailColor(val.r, val.g, val.b, val.a); } if (!TryConvertByte(GetFieldOrProp(color, "r"), out var result) || !TryConvertByte(GetFieldOrProp(color, "g"), out var result2) || !TryConvertByte(GetFieldOrProp(color, "b"), out var result3) || !TryConvertByte(GetFieldOrProp(color, "a"), out var result4)) { return fallback; } return new TrailColor(result, result2, result3, result4); } private static bool TryConvertInt(object value, out int result) { try { if (value == null) { result = 0; return false; } result = Convert.ToInt32(value); return true; } catch { result = 0; return false; } } private static bool TryConvertLong(object value, out long result) { try { if (value == null) { result = 0L; return false; } result = Convert.ToInt64(value); return true; } catch { result = 0L; return false; } } private static bool TryConvertFloat(object value, out float result) { try { if (value == null) { result = 0f; return false; } result = Convert.ToSingle(value); return true; } catch { result = 0f; return false; } } private static bool TryConvertBool(object value, out bool result) { try { if (value == null) { result = false; return false; } result = Convert.ToBoolean(value); return true; } catch { result = false; return false; } } private static bool TryConvertByte(object value, out byte result) { try { if (value == null) { result = 0; return false; } result = Convert.ToByte(value); return true; } catch { result = 0; return false; } } private static int FloorToInt(float value) { return (int)Math.Floor(value); } private static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value <= max) { return value; } return max; } private static float Clamp01(float value) { return Clamp(value, 0f, 1f); } private static void LogReflectionWarning(string message) { if (!(DateTime.UtcNow < s_nextReflectionWarningUtc)) { s_nextReflectionWarningUtc = DateTime.UtcNow.AddSeconds(10.0); MelonLogger.Warning(message); } } private static void LogPlayerRenderStatus(string signature, string message) { if (IsVerboseLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!(signature == s_lastPlayerRenderStatus) || !(utcNow < s_nextPlayerRenderStatusUtc)) { s_lastPlayerRenderStatus = signature; s_nextPlayerRenderStatusUtc = utcNow.AddSeconds(4.0); MelonLogger.Msg("Player path renderer: " + message); } } } private static void LogMonsterIconStatus(string signature, string message) { if (IsVerboseLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!(utcNow < s_nextMonsterIconDebugUtc)) { s_nextMonsterIconDebugUtc = utcNow.AddSeconds(3.0); MelonLogger.Msg("Monster icon debug: " + message); } } } private static void LogPlayerIconStatus(string signature, string message) { if (IsVerboseLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!(utcNow < s_nextPlayerIconDebugUtc)) { s_nextPlayerIconDebugUtc = utcNow.AddSeconds(3.0); MelonLogger.Msg("Player icon debug: " + message); } } } private static void LogVerbose(string message) { if (IsVerboseLoggingEnabled()) { MelonLogger.Msg(message); } } private static bool IsVerboseLoggingEnabled() { if (s_verboseLogs != null) { return s_verboseLogs.Value; } return false; } private static bool IsMonsterChangeLoggingEnabled() { return IsVerboseLoggingEnabled(); } private static bool IsPlayerDebugLoggingEnabled() { if (s_debugPlayerPath == null || !s_debugPlayerPath.Value) { return IsVerboseLoggingEnabled(); } return true; } private static void LogPlayerDebug(string message) { if (IsPlayerDebugLoggingEnabled() && !(DateTime.UtcNow < s_nextPlayerDebugLogUtc)) { s_nextPlayerDebugLogUtc = DateTime.UtcNow.AddSeconds(3.0); MelonLogger.Msg("Player path debug: " + message); } } private static void AddPlayerRecorderDebug(List rows, int index, string label, string recordedKey, string localKey, string matchedKey, MonsterTrail matchedTrail, int rawPositionCount, string decision) { if (rows != null && rows.Count < 8) { bool flag = !string.IsNullOrEmpty(recordedKey) && !string.IsNullOrEmpty(localKey) && string.Equals(recordedKey, localKey, StringComparison.OrdinalIgnoreCase); rows.Add("#" + index + " label='" + SafeLogToken(label) + "' key=" + (string.IsNullOrEmpty(recordedKey) ? "{none}" : recordedKey) + " local=" + flag + " raw=" + rawPositionCount + " match=" + (string.IsNullOrEmpty(matchedKey) ? "{none}" : matchedKey) + "#" + (matchedTrail?.Count ?? 0) + " decision=" + decision); } } private static string FormatPlayerRecorderDebugRows(List rows) { if (rows == null || rows.Count == 0) { return string.Empty; } return " recorderDetails=[" + string.Join(" | ", rows) + "]"; } private static string SafeLogToken(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Replace("|", "/").Replace("\r", " ").Replace("\n", " ") .Trim(); } return string.Empty; } private static void LogMonsterDiagnostics(string signature, string message) { if (IsMonsterChangeLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!(signature == s_lastMonsterDiagnosticsStatus) || !(utcNow < s_nextMonsterDiagnosticsUtc)) { s_lastMonsterDiagnosticsStatus = signature; s_nextMonsterDiagnosticsUtc = utcNow.AddSeconds(8.0); MelonLogger.Msg("Monster path diagnostics: " + message); } } } private static void LogPlayerNameDiagnostics(string signature, string message) { if (IsPlayerDebugLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!(signature == s_lastPlayerNameDiagnosticsStatus) || !(utcNow < s_nextPlayerNameDiagnosticsUtc)) { s_lastPlayerNameDiagnosticsStatus = signature; s_nextPlayerNameDiagnosticsUtc = utcNow.AddSeconds(8.0); MelonLogger.Msg("Player name diagnostics: " + message); } } } private static void LogEndMapDiagnostics(string signature, string message) { if (IsVerboseLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!(signature == s_lastEndMapDiagnosticsStatus) || !(utcNow < s_nextEndMapDiagnosticsUtc)) { s_lastEndMapDiagnosticsStatus = signature; s_nextEndMapDiagnosticsUtc = utcNow.AddSeconds(6.0); MelonLogger.Msg("End-map path diagnostics: " + message); } } } private static void LogNameListDiagnostics(string signature, string message) { if (IsVerboseLoggingEnabled()) { DateTime utcNow = DateTime.UtcNow; if (!s_nextNameListDiagnosticsBySignature.TryGetValue(signature, out var value) || !(utcNow < value)) { s_nextNameListDiagnosticsBySignature[signature] = utcNow.AddSeconds(6.0); MelonLogger.Msg("Name-list diagnostics: " + message); } } } private static void Increment(Dictionary counts, string key) { if (counts != null) { key = (string.IsNullOrWhiteSpace(key) ? "unknown" : key); counts.TryGetValue(key, out var value); counts[key] = value + 1; } } private static void SetCount(Dictionary counts, string key, int amount) { if (counts != null) { counts[string.IsNullOrWhiteSpace(key) ? "unknown" : key] = amount; } } private static string FormatCounts(Dictionary counts) { if (counts == null || counts.Count == 0) { return "{}"; } return "{" + string.Join(", ", from p in counts orderby p.Key select p.Key + "=" + p.Value) + "}"; } }