Decompiled source of Denis UI Killfeed v1.2.0

plugins/DenisUIKillFeed/DenisUIKillFeed.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("DenisUIKillFeed")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+0d77aba4ef99a87d6be4daeb81b0de1f2e0c0dca")]
[assembly: AssemblyProduct("DenisUIKillFeed")]
[assembly: AssemblyTitle("DenisUIKillFeed")]
[assembly: AssemblyVersion("1.2.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DenisUIKillFeed
{
	internal static class EnemyDeathEvents
	{
		public class EnemyDeathEventData
		{
			public Enemy Enemy { get; set; }

			public EnemyParent Parent { get; set; }

			public string NormalizedName { get; set; }

			public float TimeOfDeath { get; set; }

			public int LastHealth { get; set; }
		}

		public static UnityEvent<Enemy, EnemyParent, string> OnEnemyDeath { get; } = new UnityEvent<Enemy, EnemyParent, string>();


		public static event Action<EnemyDeathEventData> OnEnemyDeathDetailed;

		internal static void RaiseEnemyDeath(Enemy enemy, EnemyParent parent, string normalizedName, float timeOfDeath, int lastHealth)
		{
			try
			{
				OnEnemyDeath?.Invoke(enemy, parent, normalizedName);
				EnemyDeathEvents.OnEnemyDeathDetailed?.Invoke(new EnemyDeathEventData
				{
					Enemy = enemy,
					Parent = parent,
					NormalizedName = normalizedName,
					TimeOfDeath = timeOfDeath,
					LastHealth = lastHealth
				});
			}
			catch (Exception arg)
			{
				Debug.LogError((object)$"[EnemyDeathEvents] Exception: {arg}");
			}
		}
	}
	internal static class EnemyFeedTracker
	{
		private static float lastFullCheckTime;

		private const float FULL_CHECK_INTERVAL = 0.15f;

		internal static Enemy GetEnemyFromParent(EnemyParent enemyParent)
		{
			try
			{
				object? obj = ReflectionCache.EnemyParentEnemyField?.GetValue(enemyParent);
				return (Enemy)((obj is Enemy) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		internal static bool GetEnemyHasHealth(Enemy enemy)
		{
			try
			{
				if ((Object)(object)enemy == (Object)null)
				{
					return false;
				}
				object obj = ReflectionCache.EnemyHasHealthField?.GetValue(enemy);
				if (obj is bool)
				{
					return (bool)obj;
				}
			}
			catch
			{
			}
			return false;
		}

		internal static EnemyHealth GetEnemyHealth(Enemy enemy)
		{
			if ((Object)(object)enemy == (Object)null)
			{
				return null;
			}
			try
			{
				FieldInfo enemyHealthRefField = ReflectionCache.EnemyHealthRefField;
				if (enemyHealthRefField != null)
				{
					object? value = enemyHealthRefField.GetValue(enemy);
					return (EnemyHealth)((value is EnemyHealth) ? value : null);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[DenisUIKillFeed] Failed to get Enemy.Health: " + ex.Message));
			}
			return null;
		}

		internal static int GetEnemyHealthCurrent(EnemyHealth health)
		{
			try
			{
				if ((Object)(object)health == (Object)null)
				{
					return 0;
				}
				object obj = ReflectionCache.EnemyHealthCurrentField?.GetValue(health);
				if (obj is int)
				{
					return (int)obj;
				}
			}
			catch
			{
			}
			return 0;
		}

		internal static bool GetEnemyHealthDead(EnemyHealth health)
		{
			try
			{
				if ((Object)(object)health == (Object)null)
				{
					return false;
				}
				object obj = ReflectionCache.EnemyHealthDeadField?.GetValue(health);
				if (obj is bool)
				{
					return (bool)obj;
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void ObserveEnemiesFromDirector()
		{
			try
			{
				EnemyDirector instance = EnemyDirector.instance;
				if (!((Object)(object)instance == (Object)null) && instance.enemiesSpawned != null && ShouldRunFullCheck())
				{
					RunFullEnemyStateCheck(instance);
					CleanupDespawnedEnemies(instance);
				}
			}
			catch (Exception arg)
			{
				Debug.LogError((object)$"[DenisUIKillFeed] ObserveEnemiesFromDirector error: {arg}");
			}
		}

		private static void CleanupDespawnedEnemies(EnemyDirector director)
		{
			List<int> list = new List<int>();
			foreach (KeyValuePair<int, ObservedEnemyState> enemyState in Plugin.EnemyStates)
			{
				int id = enemyState.Key;
				ObservedEnemyState value = enemyState.Value;
				if (!director.enemiesSpawned.Any((EnemyParent ep) => (Object)(object)ep != (Object)null && ((Object)ep).GetInstanceID() == id) && value.LastHealth > 0)
				{
					list.Add(id);
				}
			}
			foreach (int item in list)
			{
				Plugin.EnemyStates.Remove(item);
			}
		}

		private static bool ShouldRunFullCheck()
		{
			if (Time.time - lastFullCheckTime >= 0.15f)
			{
				lastFullCheckTime = Time.time;
				return true;
			}
			return false;
		}

		private static void RunFullEnemyStateCheck(EnemyDirector director)
		{
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			foreach (EnemyParent item in director.enemiesSpawned)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				int instanceID = ((Object)item).GetInstanceID();
				Enemy enemyFromParent = GetEnemyFromParent(item);
				EnemyHealth enemyHealth = GetEnemyHealth(enemyFromParent);
				int num = (((Object)(object)enemyHealth != (Object)null && GetEnemyHasHealth(enemyFromParent)) ? GetEnemyHealthCurrent(enemyHealth) : 0);
				if (!Plugin.EnemyStates.TryGetValue(instanceID, out var value))
				{
					ObservedEnemyState newState = new ObservedEnemyState
					{
						EnemyParent = item,
						Name = EnemyNameDirectory.Normalize(item.enemyName ?? "Enemy"),
						LastHealth = num,
						FirstSeenAt = Time.time
					};
					Plugin.EnemyStates[instanceID] = newState;
					if (!((Object)(object)enemyHealth != (Object)null))
					{
						continue;
					}
					try
					{
						object? obj = ReflectionCache.EnemyHealthOnDeathField?.GetValue(enemyHealth);
						UnityEvent val = (UnityEvent)((obj is UnityEvent) ? obj : null);
						if (val != null)
						{
							UnityAction val2 = (UnityAction)delegate
							{
								HandleEnemyDeath(newState);
							};
							val.AddListener(val2);
							newState.DeathListener = val2;
						}
					}
					catch
					{
					}
					continue;
				}
				value.EnemyParent = item;
				if (num > 0 && value.LastHealth <= 0)
				{
					value.LastHealth = num;
				}
				if (num <= 0 && value.LastHealth > 0)
				{
					if (Plugin.ShowEnemyKillsConfig.Value && Time.time - Plugin.LevelStartTime >= 1f)
					{
						Plugin.AddEntry(Plugin.ApplyHostSyncOrCustomName(value.Name) + " killed", new Color(0.96f, 0.82f, 0.28f, 1f));
					}
					EnemyDeathEvents.RaiseEnemyDeath(enemyFromParent, item, value.Name, Time.time, value.LastHealth);
				}
				value.LastHealth = num;
			}
		}

		private static void HandleEnemyDeath(ObservedEnemyState state)
		{
		}
	}
	internal static class EnemyNameDirectory
	{
		public static readonly Dictionary<string, string> NameMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "Animal", "Animal" },
			{ "EnemyAnimal", "Animal" },
			{ "Bang", "Bang" },
			{ "EnemyBang", "Bang" },
			{ "Beamer", "Clown" },
			{ "EnemyBeamer", "Clown" },
			{ "Clown", "Clown" },
			{ "BirthdayBoy", "Birthday Boy" },
			{ "EnemyBirthdayBoy", "Birthday Boy" },
			{ "Birthday Boy", "Birthday Boy" },
			{ "BombThrower", "Cleanup Crew" },
			{ "Bomb Thrower", "Cleanup Crew" },
			{ "EnemyBombThrower", "Cleanup Crew" },
			{ "Cleanup Crew", "Cleanup Crew" },
			{ "Bowtie", "Bowtie" },
			{ "EnemyBowtie", "Bowtie" },
			{ "CeilingEye", "Peeper" },
			{ "Ceiling Eye", "Peeper" },
			{ "EnemyCeilingEye", "Peeper" },
			{ "Peeper", "Peeper" },
			{ "Duck", "Apex Predator" },
			{ "EnemyDuck", "Apex Predator" },
			{ "Apex Predator", "Apex Predator" },
			{ "Elsa", "Elsa" },
			{ "EnemyElsa", "Elsa" },
			{ "Floater", "Mentalist" },
			{ "EnemyFloater", "Mentalist" },
			{ "Mentalist", "Mentalist" },
			{ "Gnome", "Gnome" },
			{ "EnemyGnome", "Gnome" },
			{ "HeadController", "Headman" },
			{ "Head", "Headman" },
			{ "EnemyHeadController", "Headman" },
			{ "Headman", "Headman" },
			{ "HeadGrabber", "Headgrab" },
			{ "Head Grabber", "Headgrab" },
			{ "EnemyHeadGrabber", "Headgrab" },
			{ "Headgrab", "Headgrab" },
			{ "HeartHugger", "Heart Hugger" },
			{ "EnemyHeartHugger", "Heart Hugger" },
			{ "Hidden", "Hidden" },
			{ "EnemyHidden", "Hidden" },
			{ "Hunter", "Huntsman" },
			{ "EnemyHunter", "Huntsman" },
			{ "Huntsman", "Huntsman" },
			{ "Oogly", "Oogly" },
			{ "EnemyOogly", "Oogly" },
			{ "Robe", "Robe" },
			{ "EnemyRobe", "Robe" },
			{ "Runner", "Reaper" },
			{ "EnemyRunner", "Reaper" },
			{ "Reaper", "Reaper" },
			{ "Shadow", "Loom" },
			{ "EnemyShadow", "Loom" },
			{ "Loom", "Loom" },
			{ "SlowMouth", "Spewer" },
			{ "Slow Mouth", "Spewer" },
			{ "EnemySlowMouth", "Spewer" },
			{ "Spewer", "Spewer" },
			{ "SlowWalker", "Trudge" },
			{ "Slow Walker", "Trudge" },
			{ "EnemySlowWalker", "Trudge" },
			{ "Trudge", "Trudge" },
			{ "Spinny", "Gambit" },
			{ "EnemySpinny", "Gambit" },
			{ "Gambit", "Gambit" },
			{ "ThinMan", "Shadow Child" },
			{ "Thin Man", "Shadow Child" },
			{ "EnemyThinMan", "Shadow Child" },
			{ "Shadow Child", "Shadow Child" },
			{ "Tick", "Tick" },
			{ "EnemyTick", "Tick" },
			{ "Tricycle", "Bella" },
			{ "EnemyTricycle", "Bella" },
			{ "Bella", "Bella" },
			{ "Tumbler", "Chef" },
			{ "EnemyTumbler", "Chef" },
			{ "Chef", "Chef" },
			{ "Upscream", "Upscream" },
			{ "EnemyUpscream", "Upscream" },
			{ "ValuableThrower", "Rugrat" },
			{ "Valuable Thrower", "Rugrat" },
			{ "EnemyValuableThrower", "Rugrat" },
			{ "Rugrat", "Rugrat" }
		};

		public static string Normalize(string rawName)
		{
			if (string.IsNullOrWhiteSpace(rawName))
			{
				return "Unknown";
			}
			if (NameMapping.TryGetValue(rawName, out var value))
			{
				return value;
			}
			Debug.LogWarning((object)("[EnemyNameDirectory] Unknown enemy name: '" + rawName + "' → Unknown"));
			return "Unknown";
		}

		public static IEnumerable<string> GetAllDisplayNames()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string value in NameMapping.Values)
			{
				hashSet.Add(value);
			}
			List<string> list = new List<string>(hashSet);
			list.Sort();
			return list;
		}
	}
	internal static class HostSyncManager
	{
		[Serializable]
		internal sealed class SerializedMonsterNames
		{
			[SerializeField]
			public SerializedNamePair[] names = Array.Empty<SerializedNamePair>();
		}

		[Serializable]
		internal sealed class SerializedNamePair
		{
			[SerializeField]
			public string Key;

			[SerializeField]
			public string Value;
		}

		internal static readonly Dictionary<string, string> HostMonsterNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private const string HostMonsterNamesPropertyKey = "denis.killfeed.hostnames.v1";

		private static string lastKnownHostNamesJson = string.Empty;

		internal static void PublishHostMonsterNames(Dictionary<string, string> customNames)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_006b: Expected O, but got Unknown
			if (!PhotonNetwork.InRoom || !PhotonNetwork.LocalPlayer.IsMasterClient || customNames.Count == 0)
			{
				return;
			}
			try
			{
				string value = JsonUtility.ToJson((object)new SerializedMonsterNames
				{
					names = customNames.Select((KeyValuePair<string, string> kvp) => new SerializedNamePair
					{
						Key = kvp.Key,
						Value = kvp.Value
					}).ToArray()
				});
				Hashtable val = new Hashtable();
				((Dictionary<object, object>)val).Add((object)"denis.killfeed.hostnames.v1", (object)value);
				Hashtable val2 = val;
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null)
				{
					currentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[DenisUIKillFeed] Failed to publish host monster names: " + ex.Message));
			}
		}

		internal static void UpdateHostMonsterNamesFromRoom()
		{
			if (!PhotonNetwork.InRoom)
			{
				lastKnownHostNamesJson = string.Empty;
				HostMonsterNames.Clear();
				return;
			}
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				object value;
				if (((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null) == null)
				{
					lastKnownHostNamesJson = string.Empty;
					HostMonsterNames.Clear();
				}
				else if (!((Dictionary<object, object>)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"denis.killfeed.hostnames.v1", out value) || !(value is string text) || string.IsNullOrWhiteSpace(text))
				{
					lastKnownHostNamesJson = string.Empty;
					HostMonsterNames.Clear();
				}
				else
				{
					if (text == lastKnownHostNamesJson)
					{
						return;
					}
					lastKnownHostNamesJson = text;
					HostMonsterNames.Clear();
					SerializedMonsterNames serializedMonsterNames = JsonUtility.FromJson<SerializedMonsterNames>(text);
					if (serializedMonsterNames?.names == null)
					{
						return;
					}
					SerializedNamePair[] names = serializedMonsterNames.names;
					foreach (SerializedNamePair serializedNamePair in names)
					{
						if (!string.IsNullOrEmpty(serializedNamePair.Key) && !string.IsNullOrEmpty(serializedNamePair.Value))
						{
							HostMonsterNames[serializedNamePair.Key] = serializedNamePair.Value;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[DenisUIKillFeed] Failed to parse host monster names: " + ex.Message));
			}
		}
	}
	public static class KillFeedAPI
	{
		public class EnemyPoolData
		{
			public string EnemyName { get; set; }

			public string InternalName { get; set; }

			public string ClassName { get; set; }

			public GameObject GameObject { get; set; }

			public EnemyParent EnemyParent { get; set; }

			public int InstanceId { get; set; }
		}

		public static event Action<List<EnemyPoolData>> OnEnemyPoolReady;

		internal static void RaiseEnemyPoolReady(List<EnemyPoolData> enemies)
		{
			try
			{
				if (enemies == null)
				{
					enemies = new List<EnemyPoolData>();
				}
				KillFeedAPI.OnEnemyPoolReady?.Invoke(enemies);
			}
			catch (Exception arg)
			{
				Debug.LogError((object)$"[KillFeedAPI] Exception: {arg}");
			}
		}

		public static string GetCustomEnemyName(string rawEnemyName)
		{
			return Plugin.GetCustomEnemyName(rawEnemyName);
		}
	}
	internal static class KillfeedConstants
	{
		internal const float HudGraceAfterLevelEnds = 8f;

		internal const float DisconnectConfirmSeconds = 1.25f;

		internal const float DisconnectStartupGuardSeconds = 4f;

		internal const float HudSlideSeconds = 0.18f;

		internal const float EntryFadeInSeconds = 0.16f;

		internal const float EntryFadeOutSeconds = 0.24f;

		internal const float EnemyObserveIntervalSeconds = 0.15f;
	}
	[HarmonyPatch(typeof(RoundDirector))]
	internal static class KillFeedHud
	{
		private static GameObject cachedGameHud;

		private static GameObject cachedTaxHaul;

		[HarmonyPatch("Update")]
		[HarmonyPostfix]
		private static void Update_Postfix()
		{
			bool flag = SemiFunc.RunIsLevel();
			if (flag)
			{
				Plugin.LastLevelActiveAt = Time.time;
			}
			if (!EnsureHud())
			{
				return;
			}
			if (!flag)
			{
				if (Time.time - Plugin.LastLevelActiveAt <= 8f)
				{
					UpdateKillFeedHud();
					return;
				}
				SetInactive();
				Plugin.ResetObservedState();
				return;
			}
			PlayerFeedTracker.ObservePlayers();
			if (Time.time - Plugin.LastEnemyObserveAt >= 0.15f)
			{
				Plugin.LastEnemyObserveAt = Time.time;
				EnemyFeedTracker.ObserveEnemiesFromDirector();
			}
			UpdateKillFeedHud();
		}

		internal static bool EnsureHud()
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Expected O, but got Unknown
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)cachedGameHud == (Object)null)
			{
				cachedGameHud = GameObject.Find("Game Hud");
			}
			if ((Object)(object)cachedTaxHaul == (Object)null)
			{
				cachedTaxHaul = GameObject.Find("Tax Haul");
			}
			if ((Object)(object)cachedGameHud == (Object)null || (Object)(object)cachedTaxHaul == (Object)null)
			{
				return false;
			}
			TMP_Text component = cachedTaxHaul.GetComponent<TMP_Text>();
			TMP_FontAsset val = ((component != null) ? component.font : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if ((Object)(object)Plugin.FeedInstance == (Object)null)
			{
				Plugin.FeedInstance = new GameObject("Denis UI Killfeed HUD");
				Plugin.FeedInstance.SetActive(false);
				Plugin.FeedInstance.transform.SetParent(cachedGameHud.transform, false);
				Plugin.FeedText = Plugin.FeedInstance.AddComponent<TextMeshProUGUI>();
				((TMP_Text)Plugin.FeedText).font = val;
				((TMP_Text)Plugin.FeedText).fontSize = 24f;
				((TMP_Text)Plugin.FeedText).enableWordWrapping = false;
				((TMP_Text)Plugin.FeedText).alignment = (TextAlignmentOptions)260;
				((TMP_Text)Plugin.FeedText).horizontalAlignment = (HorizontalAlignmentOptions)4;
				((TMP_Text)Plugin.FeedText).verticalAlignment = (VerticalAlignmentOptions)256;
				((Graphic)Plugin.FeedText).raycastTarget = false;
				((TMP_Text)Plugin.FeedText).margin = Vector4.zero;
				((TMP_Text)Plugin.FeedText).characterSpacing = 0f;
				((TMP_Text)Plugin.FeedText).wordSpacing = 0f;
				((TMP_Text)Plugin.FeedText).fontStyle = (FontStyles)0;
				ContentSizeFitter obj = Plugin.FeedInstance.AddComponent<ContentSizeFitter>();
				obj.verticalFit = (FitMode)2;
				obj.horizontalFit = (FitMode)0;
				RectTransform component2 = Plugin.FeedInstance.GetComponent<RectTransform>();
				component2.pivot = new Vector2(1f, 1f);
				component2.anchorMin = new Vector2(1f, 1f);
				component2.anchorMax = new Vector2(1f, 1f);
				component2.sizeDelta = new Vector2(520f, 220f);
				component2.anchoredPosition = new Vector2(-10f, -250f);
			}
			else if ((Object)(object)Plugin.FeedText != (Object)null && (Object)(object)((TMP_Text)Plugin.FeedText).transform.parent != (Object)(object)cachedGameHud.transform)
			{
				((TMP_Text)Plugin.FeedText).transform.SetParent(cachedGameHud.transform, false);
			}
			return true;
		}

		internal static void UpdateKillFeedHud()
		{
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			Plugin.Entries.RemoveAll((KillFeedEntry e) => Time.time > e.ExpiresAt);
			if (Plugin.Entries.Count == 0)
			{
				Plugin.FeedInstance.SetActive(false);
				Plugin.LastRenderedFeedText = string.Empty;
				Plugin.LastRenderedFontSize = -1f;
				Plugin.HudShowStartedAt = -100000f;
				return;
			}
			bool activeSelf = Plugin.FeedInstance.activeSelf;
			Plugin.FeedInstance.SetActive(true);
			if (!activeSelf)
			{
				Plugin.HudShowStartedAt = Time.time;
			}
			RectTransform component = Plugin.FeedInstance.GetComponent<RectTransform>();
			float num = Mathf.Clamp01(Mathf.Max(0f, Time.time - Plugin.HudShowStartedAt) / 0.18f);
			float num2 = 1f - Mathf.Pow(1f - num, 3f);
			float num3 = Mathf.Lerp(24f, 0f, num2);
			component.anchoredPosition = new Vector2(-10f + num3, -250f);
			int num4 = Mathf.Clamp((Plugin.Entries.Max((KillFeedEntry e) => e.Text.Length) - 1) / 46, 0, 3);
			float num5 = Mathf.Clamp(Plugin.TextScaleConfig?.Value ?? 1f, 0.7f, 1f);
			float num6 = Plugin.MobHudSizes[num4] * num5;
			if (!Mathf.Approximately(Plugin.LastRenderedFontSize, num6))
			{
				((TMP_Text)Plugin.FeedText).fontSize = num6;
				((TMP_Text)Plugin.FeedText).lineSpacing = (0f - num6) * 1.05f;
				Plugin.LastRenderedFontSize = num6;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < Plugin.Entries.Count; i++)
			{
				KillFeedEntry killFeedEntry = Plugin.Entries[i];
				if (i > 0)
				{
					stringBuilder.Append('\n');
				}
				float num7 = Mathf.Max(0f, Time.time - killFeedEntry.CreatedAt);
				float num8 = Mathf.Max(0f, killFeedEntry.ExpiresAt - Time.time);
				float num9 = Mathf.Clamp01(num7 / 0.16f);
				float num10 = Mathf.Clamp01(num8 / 0.24f);
				string value = Mathf.Clamp(Mathf.RoundToInt(Mathf.Min(num9, num10) * 255f), 0, 255).ToString("X2");
				stringBuilder.Append("<color=#").Append(killFeedEntry.Color).Append(value)
					.Append('>')
					.Append(killFeedEntry.Text)
					.Append("</color>");
			}
			string text = stringBuilder.ToString();
			if (text != Plugin.LastRenderedFeedText)
			{
				((TMP_Text)Plugin.FeedText).text = text;
				Plugin.LastRenderedFeedText = text;
			}
		}

		internal static void SetInactive()
		{
			if ((Object)(object)Plugin.FeedInstance != (Object)null)
			{
				Plugin.FeedInstance.SetActive(false);
			}
		}

		internal static void ClearHudCache()
		{
			cachedGameHud = null;
			cachedTaxHaul = null;
		}
	}
	internal sealed class KillFeedEntry
	{
		public string Text;

		public string Color;

		public float ExpiresAt;

		public float CreatedAt;
	}
	internal sealed class ObservedPlayerState
	{
		public PlayerAvatar Avatar;

		public string Name;

		public bool WasDead;

		public float LastDeathAt;
	}
	internal sealed class PendingDisconnectState
	{
		public string Name;

		public float MissingSince;

		public bool HadRecentDeath;
	}
	internal sealed class ObservedEnemyState
	{
		public EnemyParent EnemyParent;

		public string Name;

		public int LastHealth;

		public float FirstSeenAt;

		public UnityAction DeathListener;
	}
	internal static class PlayerFeedTracker
	{
		[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
		internal static class PlayerAvatarDeathPatch
		{
			[HarmonyPostfix]
			private static void Postfix(PlayerAvatar __instance)
			{
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				string text = ResolvePlayerName(__instance);
				string item = SanitizeDisplayName(text);
				if (!Plugin.PlayersWithDisconnectFeed.Contains(item) && Plugin.ShowPlayerDeathsConfig.Value && Time.time - Plugin.LevelStartTime >= 1f)
				{
					Plugin.AddEntry(text + " died", new Color(0.7f, 0.23f, 0.23f, 1f));
				}
				int num = (((Object)(object)__instance != (Object)null) ? ((Object)__instance).GetInstanceID() : 0);
				if (num != 0 && Plugin.PlayerStates.TryGetValue(num, out var value))
				{
					value.WasDead = true;
					value.LastDeathAt = Time.time;
				}
			}
		}

		private static readonly HashSet<int> seenPlayerIds = new HashSet<int>();

		internal static string ResolvePlayerName(PlayerAvatar avatar)
		{
			if ((Object)(object)avatar == (Object)null)
			{
				return "Player";
			}
			try
			{
				string raw = ReflectionCache.PlayerNameField?.GetValue(avatar) as string;
				raw = SanitizeDisplayName(raw);
				if (!string.IsNullOrWhiteSpace(raw))
				{
					return raw;
				}
			}
			catch
			{
			}
			return SanitizeDisplayName((!string.IsNullOrWhiteSpace(((Object)avatar).name)) ? ((Object)avatar).name : "Player");
		}

		internal static string SanitizeDisplayName(string raw)
		{
			if (string.IsNullOrEmpty(raw))
			{
				return raw;
			}
			StringBuilder stringBuilder = new StringBuilder(raw.Length);
			foreach (char c in raw)
			{
				if (!char.IsControl(c) && CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.Format)
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString().Trim();
		}

		internal static bool IsPlayerDead(PlayerAvatar avatar)
		{
			if ((Object)(object)avatar == (Object)null)
			{
				return false;
			}
			try
			{
				object obj = ReflectionCache.PlayerDeadSetField?.GetValue(avatar);
				if (obj is bool)
				{
					return (bool)obj;
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void ObservePlayers()
		{
			//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
			seenPlayerIds.Clear();
			try
			{
				List<PlayerAvatar> list = SemiFunc.PlayerGetAll();
				if (list == null)
				{
					return;
				}
				foreach (PlayerAvatar item in list)
				{
					if (!((Object)(object)item == (Object)null))
					{
						int instanceID = ((Object)item).GetInstanceID();
						seenPlayerIds.Add(instanceID);
						string text = ResolvePlayerName(item);
						bool wasDead = IsPlayerDead(item);
						if (!Plugin.PlayerStates.TryGetValue(instanceID, out var value))
						{
							value = new ObservedPlayerState
							{
								Avatar = item,
								Name = text,
								WasDead = wasDead
							};
							Plugin.PlayerStates[instanceID] = value;
						}
						else
						{
							value.Avatar = item;
							value.Name = text;
						}
						Plugin.PlayerStatesByName[text] = value;
						Plugin.PendingDisconnects.Remove(text);
					}
				}
			}
			catch (Exception arg)
			{
				Debug.LogError((object)$"[DenisUIKillFeed] ObservePlayers error: {arg}");
			}
			foreach (int item2 in Plugin.PlayerStates.Keys.Where((int id) => !seenPlayerIds.Contains(id)).ToList())
			{
				ObservedPlayerState observedPlayerState = Plugin.PlayerStates[item2];
				if (observedPlayerState != null && !string.IsNullOrWhiteSpace(observedPlayerState.Name) && !Plugin.SceneResetInProgress)
				{
					bool hadRecentDeath = observedPlayerState.LastDeathAt > 0f && Time.time - observedPlayerState.LastDeathAt <= 2.5f;
					string text2 = SanitizeDisplayName(observedPlayerState.Name);
					if (!string.IsNullOrWhiteSpace(text2) && !Plugin.PendingDisconnects.ContainsKey(text2))
					{
						Plugin.PendingDisconnects[text2] = new PendingDisconnectState
						{
							Name = text2,
							MissingSince = Time.time,
							HadRecentDeath = hadRecentDeath
						};
					}
				}
				if (observedPlayerState != null && !string.IsNullOrWhiteSpace(observedPlayerState.Name))
				{
					Plugin.PlayerStatesByName.Remove(observedPlayerState.Name);
				}
				Plugin.PlayerStates.Remove(item2);
			}
			foreach (string item3 in Plugin.PendingDisconnects.Keys.ToList())
			{
				if (Plugin.PlayerStatesByName.ContainsKey(item3))
				{
					Plugin.PendingDisconnects.Remove(item3);
				}
				else
				{
					if (Time.time - Plugin.LevelStartTime < 4f)
					{
						continue;
					}
					PendingDisconnectState pendingDisconnectState = Plugin.PendingDisconnects[item3];
					if (Time.time - pendingDisconnectState.MissingSince < 1.25f)
					{
						continue;
					}
					if (Plugin.RecentlyDisconnectedPlayers.Add(pendingDisconnectState.Name))
					{
						if (Plugin.ShowDisconnectsConfig.Value)
						{
							Plugin.AddEntry(pendingDisconnectState.Name + " disconnected", new Color(0.58f, 0.58f, 0.62f, 1f));
						}
						Plugin.PlayersWithDisconnectFeed.Add(pendingDisconnectState.Name);
					}
					Plugin.PendingDisconnects.Remove(item3);
				}
			}
			if (Plugin.SceneResetInProgress && seenPlayerIds.Count > 0)
			{
				Plugin.SceneResetInProgress = false;
			}
		}
	}
	[BepInPlugin("denis.repo.denisuikillfeed", "Denis UI Killfeed", "1.1.2")]
	public class Plugin : BaseUnityPlugin
	{
		internal static GameObject FeedInstance;

		internal static TextMeshProUGUI FeedText;

		internal static readonly List<KillFeedEntry> Entries = new List<KillFeedEntry>();

		internal static readonly Dictionary<int, ObservedPlayerState> PlayerStates = new Dictionary<int, ObservedPlayerState>();

		internal static readonly Dictionary<string, ObservedPlayerState> PlayerStatesByName = new Dictionary<string, ObservedPlayerState>(StringComparer.OrdinalIgnoreCase);

		internal static readonly HashSet<string> RecentlyDisconnectedPlayers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		internal static readonly HashSet<string> PlayersWithDisconnectFeed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		internal static readonly Dictionary<string, PendingDisconnectState> PendingDisconnects = new Dictionary<string, PendingDisconnectState>(StringComparer.OrdinalIgnoreCase);

		internal static readonly Dictionary<int, ObservedEnemyState> EnemyStates = new Dictionary<int, ObservedEnemyState>();

		internal static readonly float[] MobHudSizes = new float[4] { 24f, 18f, 14f, 11f };

		internal static bool SceneResetInProgress;

		internal static float LastLevelActiveAt;

		internal static float LevelStartTime;

		internal static float HudShowStartedAt = -100000f;

		internal static float LastEnemyObserveAt = -100000f;

		internal static string LastRenderedFeedText = string.Empty;

		internal static float LastRenderedFontSize = -1f;

		internal static ConfigEntry<bool> ShowEnemyKillsConfig;

		internal static ConfigEntry<bool> ShowPlayerDeathsConfig;

		internal static ConfigEntry<bool> ShowDisconnectsConfig;

		internal static ConfigEntry<float> TextScaleConfig;

		internal static ConfigEntry<bool> HostSyncNamesConfig;

		internal static Dictionary<string, ConfigEntry<string>> MonsterNameConfigs = new Dictionary<string, ConfigEntry<string>>();

		private static Dictionary<string, string> cachedCustomNames;

		private static int lastConfigCheckFrame = -1;

		private static bool hasPublishedNamesThisLevel = false;

		private static bool hasPublishedEnemyPoolThisLevel = false;

		internal static Plugin Instance { get; private set; }

		internal Harmony Harmony { get; private set; }

		private void Awake()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Expected O, but got Unknown
			Instance = this;
			((Component)this).transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			TextScaleConfig = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Text Scale", 1f, new ConfigDescription("Scales the killfeed text size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.7f, 1f), Array.Empty<object>()));
			ShowEnemyKillsConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enemy deaths", true, "Show enemy death events in the killfeed.");
			ShowPlayerDeathsConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Player deaths", true, "Show player death events in the killfeed.");
			ShowDisconnectsConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Player disconnects", true, "Show player disconnect events in the killfeed.");
			HostSyncNamesConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Custom Names", "Host Sync", true, "When enabled, use custom enemy names from the host during multiplayer. Hosts always broadcast their names; clients sync if this is ON.");
			foreach (string allDisplayName in EnemyNameDirectory.GetAllDisplayNames())
			{
				MonsterNameConfigs[allDisplayName] = ((BaseUnityPlugin)this).Config.Bind<string>("Custom Names", allDisplayName, allDisplayName, new ConfigDescription("Customize how '" + allDisplayName + "' appears in killfeed.", (AcceptableValueBase)null, Array.Empty<object>()));
			}
			Harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
			Harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Denis UI Killfeed loaded");
		}

		private void Update()
		{
			EnemyFeedTracker.ObserveEnemiesFromDirector();
			if (!hasPublishedEnemyPoolThisLevel && LevelStartTime > 0f && Time.time - LevelStartTime >= 0.2f)
			{
				PublishEnemyPool();
				hasPublishedEnemyPoolThisLevel = true;
			}
			if (!hasPublishedNamesThisLevel && LevelStartTime > 0f && Time.time - LevelStartTime >= 0.5f)
			{
				HostSyncManager.PublishHostMonsterNames(GetCachedCustomMonsterNames());
				hasPublishedNamesThisLevel = true;
			}
			HostSyncManager.UpdateHostMonsterNamesFromRoom();
		}

		private static Dictionary<string, string> GetCachedCustomMonsterNames()
		{
			int frameCount = Time.frameCount;
			if (frameCount - lastConfigCheckFrame >= 30)
			{
				cachedCustomNames = ParseCustomMonsterNames();
				lastConfigCheckFrame = frameCount;
			}
			return cachedCustomNames ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		}

		private void OnDestroy()
		{
			Harmony harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		internal static Dictionary<string, string> ParseCustomMonsterNames()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, ConfigEntry<string>> monsterNameConfig in MonsterNameConfigs)
			{
				string key = ((ConfigEntryBase)monsterNameConfig.Value).Definition.Key;
				string value = monsterNameConfig.Value.Value;
				if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value))
				{
					dictionary[key] = value.Trim();
				}
			}
			return dictionary;
		}

		internal static string NormalizeEnemyName(string rawName)
		{
			return EnemyNameDirectory.Normalize(rawName);
		}

		internal static string ApplyCustomMonsterName(string resolvedName)
		{
			if (string.IsNullOrWhiteSpace(resolvedName))
			{
				return resolvedName;
			}
			string text = NormalizeEnemyName(resolvedName);
			if (GetCachedCustomMonsterNames().TryGetValue(text, out var value))
			{
				return value;
			}
			return text;
		}

		internal static string ApplyHostSyncOrCustomName(string resolvedName)
		{
			if (string.IsNullOrWhiteSpace(resolvedName))
			{
				return resolvedName;
			}
			string text = NormalizeEnemyName(resolvedName);
			if (HostSyncNamesConfig.Value && PhotonNetwork.InRoom && !PhotonNetwork.LocalPlayer.IsMasterClient && HostSyncManager.HostMonsterNames.Count > 0 && HostSyncManager.HostMonsterNames.TryGetValue(text, out var value))
			{
				return value;
			}
			if (GetCachedCustomMonsterNames().TryGetValue(text, out var value2))
			{
				return value2;
			}
			return text;
		}

		internal static void AddEntry(string text, Color color)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			string color2 = ColorUtility.ToHtmlStringRGB(color);
			Entries.Insert(0, new KillFeedEntry
			{
				Text = text,
				Color = color2,
				ExpiresAt = Time.time + 7f,
				CreatedAt = Time.time
			});
			if (Entries.Count > 10)
			{
				Entries.RemoveRange(10, Entries.Count - 10);
			}
		}

		public static string GetCustomEnemyName(string rawEnemyName)
		{
			if (string.IsNullOrWhiteSpace(rawEnemyName))
			{
				return "Unknown";
			}
			string text = ApplyHostSyncOrCustomName(NormalizeEnemyName(rawEnemyName));
			if (text == "Unknown")
			{
				Debug.LogWarning((object)("[Plugin] GetCustomEnemyName: '" + rawEnemyName + "' → Unknown (not in directory)"));
			}
			return text;
		}

		private static void PublishEnemyPool()
		{
			try
			{
				EnemyDirector instance = EnemyDirector.instance;
				if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null || instance.enemiesSpawned.Count == 0)
				{
					KillFeedAPI.RaiseEnemyPoolReady(new List<KillFeedAPI.EnemyPoolData>());
					return;
				}
				List<KillFeedAPI.EnemyPoolData> list = new List<KillFeedAPI.EnemyPoolData>();
				foreach (EnemyParent item2 in instance.enemiesSpawned)
				{
					if (!((Object)(object)item2 == (Object)null))
					{
						try
						{
							EnemyFeedTracker.GetEnemyFromParent(item2);
							string enemyName = ApplyHostSyncOrCustomName(EnemyNameDirectory.Normalize(item2.enemyName ?? "Enemy"));
							KillFeedAPI.EnemyPoolData item = new KillFeedAPI.EnemyPoolData
							{
								EnemyName = enemyName,
								InternalName = item2.enemyName,
								ClassName = ((object)item2).GetType().Name,
								GameObject = ((Component)item2).gameObject,
								EnemyParent = item2,
								InstanceId = ((Object)item2).GetInstanceID()
							};
							list.Add(item);
						}
						catch (Exception ex)
						{
							Debug.LogWarning((object)("[Plugin] Error processing enemy in PublishEnemyPool: " + ex.Message));
						}
					}
				}
				KillFeedAPI.RaiseEnemyPoolReady(list);
			}
			catch (Exception ex2)
			{
				Debug.LogError((object)("[Plugin] Exception in PublishEnemyPool: " + ex2.Message + "\n" + ex2.StackTrace));
				KillFeedAPI.RaiseEnemyPoolReady(new List<KillFeedAPI.EnemyPoolData>());
			}
		}

		internal static void ResetObservedState()
		{
			PlayerStates.Clear();
			PlayerStatesByName.Clear();
			EnemyStates.Clear();
			RecentlyDisconnectedPlayers.Clear();
			PlayersWithDisconnectFeed.Clear();
			PendingDisconnects.Clear();
			SceneResetInProgress = true;
			LevelStartTime = Time.time;
			HudShowStartedAt = -100000f;
			LastEnemyObserveAt = -100000f;
			LastRenderedFeedText = string.Empty;
			LastRenderedFontSize = -1f;
			hasPublishedNamesThisLevel = false;
			hasPublishedEnemyPoolThisLevel = false;
			KillFeedHud.ClearHudCache();
		}
	}
	internal static class ReflectionCache
	{
		private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		internal static readonly FieldInfo PlayerNameField = typeof(PlayerAvatar).GetField("playerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo PlayerDeadSetField = typeof(PlayerAvatar).GetField("deadSet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyParentEnemyField = typeof(EnemyParent).GetField("Enemy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyParent).GetField("enemy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyParentSpawnedField = typeof(EnemyParent).GetField("Spawned", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyParent).GetField("spawned", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyHasHealthField = typeof(Enemy).GetField("HasHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(Enemy).GetField("hasHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyHealthRefField = typeof(Enemy).GetField("Health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(Enemy).GetField("health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyHealthCurrentField = typeof(EnemyHealth).GetField("healthCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyHealthDeadField = typeof(EnemyHealth).GetField("dead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static readonly FieldInfo EnemyHealthOnDeathField = typeof(EnemyHealth).GetField("onDeath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
	}
}