Decompiled source of DeathNotices v0.5.0

Mods/DeathNotices.dll

Decompiled 3 days ago
using System;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using DeathNotices;
using HarmonyLib;
using Il2CppFishNet;
using Il2CppFishNet.Object;
using Il2CppScheduleOne.Combat;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.NPCs;
using Il2CppScheduleOne.Persistence;
using Il2CppScheduleOne.PlayerScripts;
using Il2CppScheduleOne.PlayerScripts.Health;
using Il2CppScheduleOne.Police;
using Il2CppScheduleOne.UI;
using Il2CppScheduleOne.Vehicles;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using ModSettings;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Main), "Death Notices", "0.5.0", "holyfurries", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("DeathNotices")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.0.0")]
[assembly: AssemblyInformationalVersion("0.5.0+3eca97da980555afb928f4cb91ccdd7da2c04a39")]
[assembly: AssemblyProduct("DeathNotices")]
[assembly: AssemblyTitle("DeathNotices")]
[assembly: AssemblyVersion("0.5.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace DeathNotices
{
	internal enum DeathKind
	{
		Unknown,
		Gunshot,
		Explosion,
		Melee,
		SharpWeapon,
		Vehicle,
		Impact
	}
	internal enum AttackerKind
	{
		Unknown,
		Player,
		Police,
		NPC
	}
	internal readonly record struct DeathCause(DeathKind kind, string attacker = "", bool self_inflicted = false, AttackerKind attacker_kind = AttackerKind.Unknown);
	internal sealed class DeathTracker
	{
		private readonly int[] impact_ids = new int[8];

		private int death_count;

		private int impact_count;

		private int impact_index;

		private DeathCause pending_cause;

		private float pending_amount;

		private float pending_seconds = float.NegativeInfinity;

		private DeathCause last_cause;

		private float damage_seconds = float.NegativeInfinity;

		public bool is_dead { get; private set; }

		public void reset(bool alive)
		{
			death_count = 0;
			impact_count = 0;
			impact_index = 0;
			pending_cause = default(DeathCause);
			pending_amount = 0f;
			pending_seconds = float.NegativeInfinity;
			last_cause = default(DeathCause);
			damage_seconds = float.NegativeInfinity;
			is_dead = !alive;
		}

		public void revive()
		{
			int num = death_count;
			reset(alive: true);
			death_count = num;
		}

		public void observe_impact(int impact_id, float amount, DeathCause cause, float now_seconds)
		{
			if (is_dead || !float.IsFinite(amount) || amount <= 0f || !float.IsFinite(now_seconds))
			{
				return;
			}
			for (int i = 0; i < impact_count; i++)
			{
				if (impact_ids[i] == impact_id)
				{
					return;
				}
			}
			impact_ids[impact_index] = impact_id;
			impact_index = (impact_index + 1) % impact_ids.Length;
			impact_count = Math.Min(impact_ids.Length, impact_count + 1);
			pending_cause = cause;
			pending_amount = amount;
			pending_seconds = now_seconds;
		}

		public DeathCause consume_impact(float amount, float now_seconds)
		{
			float num = now_seconds - pending_seconds;
			bool num2 = !is_dead && float.IsFinite(amount) && amount > 0f && float.IsFinite(num) && num >= 0f && num <= 1.5f && Math.Abs(amount - pending_amount) <= 0.01f;
			pending_seconds = float.NegativeInfinity;
			if (!num2)
			{
				return default(DeathCause);
			}
			return pending_cause;
		}

		public void record_damage(DeathCause cause, bool fatal, float now_seconds)
		{
			if (!is_dead && float.IsFinite(now_seconds))
			{
				last_cause = (fatal ? cause : default(DeathCause));
				damage_seconds = now_seconds;
			}
		}

		public bool try_death(string victim, float now_seconds, DeathCause? current_damage, int variation, out string message)
		{
			message = "";
			if (is_dead || !float.IsFinite(now_seconds))
			{
				return false;
			}
			is_dead = true;
			float num = now_seconds - damage_seconds;
			DeathCause cause = current_damage ?? ((num >= 0f && num <= 2f) ? last_cause : default(DeathCause));
			death_count = Math.Min(death_count + 1, 1000);
			message = format_notice(victim, cause, death_count, variation);
			pending_seconds = float.NegativeInfinity;
			return true;
		}

		public static string format_notice(string victim, DeathCause cause, int death_count, int variation)
		{
			int num = (variation & 0x7FFFFFFF) % 4;
			string text = safe_name(victim);
			if (death_count >= 3 && death_count % 2 == 1 && num == 0)
			{
				return format(victim, cause) + " Keeping this notification system employed.";
			}
			string text2 = ((cause.kind != DeathKind.Unknown && cause.self_inflicted) ? (num switch
			{
				0 => "was their own worst enemy.", 
				1 => "filed a complaint against themselves.", 
				2 => "lost a fight with their own decisions.", 
				_ => "should not have been left unsupervised.", 
			}) : ((cause.kind != DeathKind.Gunshot || cause.attacker_kind != AttackerKind.Police) ? (cause.kind switch
			{
				DeathKind.Gunshot => num switch
				{
					0 => "discovered bullets are not suggestions.", 
					1 => "brought confidence to a gunfight.", 
					2 => "forgot to decline incoming ammunition.", 
					_ => "tested the wrong end of a gun.", 
				}, 
				DeathKind.Explosion => num switch
				{
					0 => "became a group project.", 
					1 => "stood inside the recommended blast radius.", 
					2 => "went out with questionable timing.", 
					_ => "has been distributed locally.", 
				}, 
				DeathKind.Melee => num switch
				{
					0 => "lost an argument at arm's length.", 
					1 => "caught hands instead of a break.", 
					2 => "failed the practical boxing exam.", 
					_ => "should have kept that thought to themselves.", 
				}, 
				DeathKind.SharpWeapon => num switch
				{
					0 => "lost a pointed discussion.", 
					1 => "found the sharp end of the situation.", 
					2 => "was not cut out for this.", 
					_ => "ignored a cutting remark.", 
				}, 
				DeathKind.Vehicle => num switch
				{
					0 => "lost the right-of-way dispute.", 
					1 => "became a speed bump.", 
					2 => "challenged traffic and finished second.", 
					_ => "forgot cars have the final say.", 
				}, 
				DeathKind.Impact => num switch
				{
					0 => "lost to an inanimate object.", 
					1 => "failed a practical physics exam.", 
					2 => "was on the receiving end of momentum.", 
					_ => "should have moved slightly to the left.", 
				}, 
				_ => num switch
				{
					0 => "has become an administrative problem.", 
					1 => "has left the group chat.", 
					2 => "has been banned from the alive casino.", 
					_ => "bet it all on red.", 
				}, 
			}) : (num switch
			{
				0 => "unsuccessfully disputed the charges.", 
				1 => "brought a complaint to a police gunfight.", 
				2 => "received the express arrest package.", 
				_ => "will not be getting their deposit back from the police.", 
			})));
			string text3 = ((cause.kind != DeathKind.Unknown && !cause.self_inflicted && !string.IsNullOrEmpty(cause.attacker)) ? (" Courtesy of " + safe_name(cause.attacker) + ".") : "");
			if (cause.attacker_kind == AttackerKind.Player && !cause.self_inflicted && cause.kind != DeathKind.Unknown && num == 0)
			{
				text2 = "discovered friendly fire isn't.";
			}
			return text + " " + text2 + text3;
		}

		public static string format(string victim, DeathCause cause)
		{
			string text = safe_name(victim);
			if (cause.kind == DeathKind.Unknown)
			{
				return text + " died.";
			}
			if (cause.self_inflicted)
			{
				return text + " died from a self-inflicted injury.";
			}
			string text2 = (string.IsNullOrEmpty(cause.attacker) ? "" : safe_name(cause.attacker));
			string text3 = cause.kind switch
			{
				DeathKind.Gunshot => "was shot", 
				DeathKind.Explosion => "was killed in an explosion", 
				DeathKind.Melee => "was beaten to death", 
				DeathKind.SharpWeapon => "was killed with a sharp weapon", 
				DeathKind.Vehicle => "was run over", 
				_ => "was killed by an impact", 
			};
			if (text2.Length == 0)
			{
				return text + " " + text3 + ".";
			}
			if (cause.kind == DeathKind.Explosion)
			{
				return $"{text} {text3} caused by {text2}.";
			}
			if (cause.kind == DeathKind.Impact)
			{
				return text + " was killed by " + text2 + ".";
			}
			return $"{text} {text3} by {text2}.";
		}

		public static string safe_name(string? value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "Someone";
			}
			StringBuilder stringBuilder = new StringBuilder(48);
			int num = Math.Min(value.Length, 128);
			for (int i = 0; i < num; i++)
			{
				if (stringBuilder.Length >= 48)
				{
					break;
				}
				char c = value[i];
				bool flag = ((c == '<' || c == '>') ? true : false);
				if (flag || char.IsControl(c) || char.GetUnicodeCategory(c) == UnicodeCategory.Format)
				{
					continue;
				}
				if (char.IsHighSurrogate(c))
				{
					if (i + 1 < num && char.IsLowSurrogate(value[i + 1]) && stringBuilder.Length <= 46)
					{
						stringBuilder.Append(c).Append(value[++i]);
					}
				}
				else if (!char.IsLowSurrogate(c))
				{
					stringBuilder.Append(c);
				}
			}
			string text = stringBuilder.ToString().Trim();
			if (text.Length != 0)
			{
				return text;
			}
			return "Someone";
		}
	}
	public sealed class Main : MelonMod
	{
		private sealed class PlayerState
		{
			public Player? player;

			public readonly DeathTracker tracker = new DeathTracker();

			public DeathCause? current_damage;
		}

		private readonly record struct DamageEvent(PlayerState? state, float health_before, bool alive_before, DeathCause cause, DeathCause? previous_damage);

		private readonly record struct DeathEvent(PlayerState? state, bool alive_before);

		private static readonly PlayerState[] players = new PlayerState[16];

		private static readonly NoticeQueue notices = new NoticeQueue();

		private static MelonPreferences_Entry<NoticePosition> notice_position = null;

		private static MelonPreferences_Entry<float> notice_margin_x = null;

		private static MelonPreferences_Entry<float> notice_margin_y = null;

		private static bool running;

		private static bool failed;

		private static bool feed_failed;

		private float next_tick_seconds;

		private float next_notice_seconds;

		private bool? logged_host;

		internal static bool ready
		{
			get
			{
				if (running && !failed && Singleton<LoadManager>.InstanceExists)
				{
					return Singleton<LoadManager>.Instance.IsGameLoaded;
				}
				return false;
			}
		}

		public override void OnInitializeMelon()
		{
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Expected O, but got Unknown
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Expected O, but got Unknown
			//IL_0208: Expected O, but got Unknown
			//IL_0208: Expected O, but got Unknown
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Expected O, but got Unknown
			//IL_0257: Expected O, but got Unknown
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Expected O, but got Unknown
			for (int i = 0; i < players.Length; i++)
			{
				players[i] = new PlayerState();
			}
			MelonPreferences_Category obj = MelonPreferences.CreateCategory("DeathNotices");
			notice_position = obj.CreateEntry<NoticePosition>("position", NoticePosition.TopCenter, "Notice position", "TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter or BottomRight", false, false, (ValueValidator)null, (string)null);
			notice_margin_x = obj.CreateEntry<float>("margin_x", 24f, "Distance from the left or right screen edge (1920x1080 units)", (string)null, false, false, (ValueValidator)null, (string)null);
			notice_margin_y = obj.CreateEntry<float>("margin_y", 72f, "Distance from the top or bottom screen edge (1920x1080 units)", (string)null, false, false, (ValueValidator)null, (string)null);
			Settings.dropdown<NoticePosition>("Death Notices", "Notice position", notice_position);
			Settings.slider("Death Notices", "Side margin", notice_margin_x, 0f, 600f, true);
			Settings.slider("Death Notices", "Top or bottom margin", notice_margin_y, 0f, 900f, true);
			((MelonEventBase<LemonAction<NoticePosition, NoticePosition>>)(object)notice_position.OnEntryValueChanged).Subscribe((LemonAction<NoticePosition, NoticePosition>)delegate
			{
				place_notices();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)notice_margin_x.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate
			{
				place_notices();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)notice_margin_y.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate
			{
				place_notices();
			}, 0, false);
			place_notices();
			((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(Player), "RpcLogic___ReceiveImpact_427288424", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Main), "observe_impact", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(PlayerHealth), "RpcLogic___TakeDamage_3505310624", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Main), "before_damage", (Type[])null), new HarmonyMethod(typeof(Main), "after_damage", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Main), "finish_damage", (Type[])null), (HarmonyMethod)null);
			((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(PlayerHealth), "RpcLogic___Die_2166136261", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Main), "before_death", (Type[])null), new HarmonyMethod(typeof(Main), "after_death", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(PlayerHealth), "RpcLogic___Revive_3848837105", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Main), "after_revive", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		public override void OnPreferencesSaved()
		{
			place_notices();
		}

		public override void OnPreferencesLoaded()
		{
			place_notices();
		}

		private static void place_notices()
		{
			if (notice_position != null)
			{
				NoticeFeed.place(new NoticePlacement(notice_position.Value, notice_margin_x.Value, notice_margin_y.Value));
			}
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			if (!(sceneName != "Main"))
			{
				clear();
				running = true;
			}
		}

		public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
		{
			if (!(sceneName != "Main"))
			{
				running = false;
				clear();
			}
		}

		private void clear()
		{
			PlayerState[] array = players;
			foreach (PlayerState obj in array)
			{
				obj.player = null;
				obj.current_damage = null;
				obj.tracker.reset(alive: true);
			}
			notices.reset();
			NoticeFeed.reset();
			failed = false;
			feed_failed = false;
			logged_host = null;
			next_tick_seconds = 0f;
			next_notice_seconds = 0f;
		}

		public override void OnUpdate()
		{
			if (!ready)
			{
				return;
			}
			try
			{
				if (!feed_failed)
				{
					NoticeFeed.update(Time.unscaledTime);
				}
				if (Time.unscaledTime < next_tick_seconds)
				{
					return;
				}
				next_tick_seconds = Time.unscaledTime + 0.25f;
				bool isServer = InstanceFinder.IsServer;
				if (logged_host != isServer)
				{
					((MelonBase)this).LoggerInstance.Msg("Death Notices: " + (isServer ? "Host" : "Client") + " listening to native death events; each installed peer displays its own notices.");
					logged_host = isServer;
				}
				int num = Math.Min(Player.PlayerList.Count, players.Length);
				for (int i = 0; i < players.Length; i++)
				{
					PlayerState playerState = players[i];
					bool flag = false;
					for (int j = 0; j < num; j++)
					{
						if ((Object)(object)Player.PlayerList[j] != (Object)null && (Object)(object)Player.PlayerList[j] == (Object)(object)playerState.player)
						{
							flag = true;
						}
					}
					if (!flag)
					{
						playerState.player = null;
						playerState.current_damage = null;
						playerState.tracker.reset(alive: true);
					}
				}
				for (int k = 0; k < num; k++)
				{
					Player val = Player.PlayerList[k];
					if ((Object)(object)val == (Object)null || (Object)(object)val.Health == (Object)null)
					{
						continue;
					}
					PlayerState playerState2 = get_player(val);
					if (playerState2 == null)
					{
						continue;
					}
					if (val.Health.IsAlive)
					{
						if (playerState2.tracker.is_dead)
						{
							playerState2.tracker.revive();
						}
					}
					else
					{
						announce(playerState2);
					}
				}
				if (!(Time.unscaledTime < next_notice_seconds) && notices.try_take(Time.unscaledTime, out string message, out string title))
				{
					show_notice(title, message);
					next_notice_seconds = Time.unscaledTime + 0.5f;
				}
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static void show_notice(string title, string message)
		{
			if (!feed_failed)
			{
				try
				{
					NoticeFeed.show(title, message, Time.unscaledTime);
					return;
				}
				catch (Exception value)
				{
					feed_failed = true;
					NoticeFeed.reset();
					MelonLogger.Warning($"Death Notices: Overlay feed failed; using native notifications for this scene: {value}");
				}
			}
			if (Singleton<NotificationsManager>.InstanceExists)
			{
				Singleton<NotificationsManager>.Instance.SendNotification(title, message, (Sprite)null, 6f, false);
			}
		}

		private static PlayerState? get_player(Player player)
		{
			PlayerState playerState = null;
			for (int i = 0; i < players.Length; i++)
			{
				PlayerState playerState2 = players[i];
				if ((Object)(object)playerState2.player != (Object)null && (Object)(object)playerState2.player == (Object)(object)player)
				{
					return playerState2;
				}
				if ((Object)(object)playerState2.player == (Object)null && playerState == null)
				{
					playerState = playerState2;
				}
			}
			if (playerState == null || (Object)(object)player.Health == (Object)null)
			{
				return null;
			}
			playerState.player = player;
			playerState.current_damage = null;
			playerState.tracker.reset(player.Health.IsAlive);
			return playerState;
		}

		private static void observe_impact(Player __instance, Impact __0)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected I4, but got Unknown
			if (!ready || (Object)(object)__instance == (Object)null || __0 == null || !float.IsFinite(__0.ImpactDamage) || __0.ImpactDamage <= 0f)
			{
				return;
			}
			try
			{
				PlayerState playerState = get_player(__instance);
				if (playerState == null)
				{
					return;
				}
				EImpactType impactType = __0.ImpactType;
				DeathKind deathKind;
				switch ((int)impactType)
				{
				case 3:
					deathKind = DeathKind.Gunshot;
					break;
				case 5:
					deathKind = DeathKind.Explosion;
					break;
				case 0:
				case 1:
					deathKind = DeathKind.Melee;
					break;
				case 2:
					deathKind = DeathKind.SharpWeapon;
					break;
				case 4:
					deathKind = DeathKind.Impact;
					break;
				default:
					deathKind = DeathKind.Unknown;
					break;
				}
				DeathKind deathKind2 = deathKind;
				string attacker = "";
				bool self_inflicted = false;
				AttackerKind attacker_kind = AttackerKind.Unknown;
				NetworkObject impactSource = __0.ImpactSource;
				if ((Object)(object)impactSource != (Object)null)
				{
					Player val = ((Component)impactSource).GetComponentInParent<Player>();
					PoliceOfficer componentInParent = ((Component)impactSource).GetComponentInParent<PoliceOfficer>();
					LandVehicle componentInParent2 = ((Component)impactSource).GetComponentInParent<LandVehicle>();
					NPC componentInParent3 = ((Component)impactSource).GetComponentInParent<NPC>();
					if ((Object)(object)componentInParent2 != (Object)null && deathKind2 == DeathKind.Impact)
					{
						deathKind2 = DeathKind.Vehicle;
						val = componentInParent2.DriverPlayer;
					}
					if ((Object)(object)val != (Object)null)
					{
						attacker_kind = AttackerKind.Player;
						self_inflicted = (Object)(object)val == (Object)(object)__instance;
						attacker = DeathTracker.safe_name(val.PlayerName);
					}
					else if ((Object)(object)componentInParent != (Object)null)
					{
						attacker_kind = AttackerKind.Police;
						attacker = "police";
					}
					else if ((Object)(object)componentInParent3 != (Object)null)
					{
						attacker_kind = AttackerKind.NPC;
						attacker = DeathTracker.safe_name(componentInParent3.FullName);
					}
				}
				playerState.tracker.observe_impact(__0.ImpactID, __0.ImpactDamage, new DeathCause(deathKind2, attacker, self_inflicted, attacker_kind), Time.unscaledTime);
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static void before_damage(PlayerHealth __instance, float __0, out DamageEvent __state)
		{
			__state = default(DamageEvent);
			if (!ready || (Object)(object)__instance.Player == (Object)null || !float.IsFinite(__0) || __0 <= 0f)
			{
				return;
			}
			try
			{
				PlayerState playerState = get_player(__instance.Player);
				if (playerState != null)
				{
					DeathCause deathCause = playerState.tracker.consume_impact(__0, Time.unscaledTime);
					__state = new DamageEvent(playerState, __instance.CurrentHealth, __instance.IsAlive, deathCause, playerState.current_damage);
					playerState.current_damage = deathCause;
				}
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static void after_damage(PlayerHealth __instance, DamageEvent __state)
		{
			if (__state.state == null)
			{
				return;
			}
			try
			{
				if (__state.alive_before && (__instance.CurrentHealth < __state.health_before || !__instance.IsAlive))
				{
					__state.state.tracker.record_damage(__state.cause, __instance.CurrentHealth <= 0f || !__instance.IsAlive, Time.unscaledTime);
				}
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static Exception? finish_damage(Exception? __exception, DamageEvent __state)
		{
			if (__state.state != null)
			{
				__state.state.current_damage = __state.previous_damage;
			}
			return __exception;
		}

		private static void before_death(PlayerHealth __instance, out DeathEvent __state)
		{
			__state = default(DeathEvent);
			if (!ready || (Object)(object)__instance.Player == (Object)null)
			{
				return;
			}
			try
			{
				__state = new DeathEvent(get_player(__instance.Player), __instance.IsAlive);
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static void after_death(PlayerHealth __instance, DeathEvent __state)
		{
			if (__state.state == null || !__state.alive_before || __instance.IsAlive)
			{
				return;
			}
			try
			{
				announce(__state.state);
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static void after_revive(PlayerHealth __instance)
		{
			if (!ready || (Object)(object)__instance.Player == (Object)null || !__instance.IsAlive)
			{
				return;
			}
			try
			{
				get_player(__instance.Player)?.tracker.revive();
			}
			catch (Exception error)
			{
				disable(error);
			}
		}

		private static void announce(PlayerState state)
		{
			if (!((Object)(object)state.player == (Object)null) && state.tracker.try_death(state.player.PlayerName, Time.unscaledTime, state.current_damage, Random.Shared.Next(8), out string message))
			{
				queue_notice("Death notice", message);
				MelonLogger.Msg("Death Notices: " + message);
			}
		}

		internal static void queue_notice(string title, string message)
		{
			notices.add(message, Time.unscaledTime, title);
		}

		private static void disable(Exception error)
		{
			if (!failed)
			{
				failed = true;
				MelonLogger.Error($"Death Notices: Disabled for this scene after a game API failure: {error}");
			}
		}
	}
	internal static class NoticeFeed
	{
		private sealed class Row
		{
			public readonly GameObject root;

			public readonly RectTransform rect;

			public readonly CanvasGroup group;

			public readonly TextMeshProUGUI text;

			public float shown_seconds = float.NegativeInfinity;

			public float height;

			public Row(GameObject root, RectTransform rect, CanvasGroup group, TextMeshProUGUI text)
			{
				this.root = root;
				this.rect = rect;
				this.group = group;
				this.text = text;
			}
		}

		private const float row_width = 640f;

		private const float row_gap = 6f;

		private const float padding_x = 18f;

		private const float padding_y = 9f;

		private const float lifetime_seconds = 7f;

		private const float fade_in_seconds = 0.2f;

		private const float fade_out_seconds = 0.6f;

		private static readonly Row?[] rows = new Row[4];

		private static GameObject? canvas_object;

		private static NoticePlacement placement = new NoticePlacement(NoticePosition.TopCenter, 24f, 72f);

		public static void reset()
		{
			if ((Object)(object)canvas_object != (Object)null)
			{
				Object.Destroy((Object)(object)canvas_object);
			}
			canvas_object = null;
			Array.Clear(rows, 0, rows.Length);
		}

		public static void place(NoticePlacement value)
		{
			placement = value.validated();
			if ((Object)(object)canvas_object != (Object)null)
			{
				layout();
			}
		}

		public static void show(string title, string message, float now_seconds)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(message) || !float.IsFinite(now_seconds))
			{
				return;
			}
			if ((Object)(object)canvas_object == (Object)null)
			{
				build();
			}
			Row row = null;
			Row[] array = rows;
			foreach (Row row2 in array)
			{
				if (row2 != null && (row == null || row2.shown_seconds < row.shown_seconds))
				{
					row = row2;
				}
			}
			if (row != null)
			{
				string text = "<b><color=#F2B84B>" + title + "</color></b>  " + message;
				float y = ((TMP_Text)row.text).GetPreferredValues(text, 604f, 0f).y;
				if (float.IsFinite(y))
				{
					((TMP_Text)row.text).text = text;
					row.height = Math.Clamp(y, 20f, 200f) + 18f;
					row.rect.sizeDelta = new Vector2(640f, row.height);
					row.shown_seconds = now_seconds;
					row.group.alpha = 0f;
					row.root.SetActive(true);
					layout();
				}
			}
		}

		public static void update(float now_seconds)
		{
			if ((Object)(object)canvas_object == (Object)null || !float.IsFinite(now_seconds))
			{
				return;
			}
			bool flag = false;
			Row[] array = rows;
			foreach (Row row in array)
			{
				if (row != null && !float.IsNegativeInfinity(row.shown_seconds))
				{
					float num = now_seconds - row.shown_seconds;
					if (num > 7f)
					{
						row.shown_seconds = float.NegativeInfinity;
						row.root.SetActive(false);
						flag = true;
					}
					else
					{
						float val = num / 0.2f;
						float val2 = (7f - num) / 0.6f;
						row.group.alpha = Math.Clamp(Math.Min(val, val2), 0f, 1f);
					}
				}
			}
			if (flag)
			{
				layout();
			}
		}

		private static void layout()
		{
			//IL_00a5: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			Row[] array = rows;
			Vector2 val = default(Vector2);
			foreach (Row row in array)
			{
				if (row == null || float.IsNegativeInfinity(row.shown_seconds))
				{
					continue;
				}
				float num = 0f;
				Row[] array2 = rows;
				foreach (Row row2 in array2)
				{
					if (row2 != null && row2 != row && !float.IsNegativeInfinity(row2.shown_seconds) && row2.shown_seconds < row.shown_seconds)
					{
						num += row2.height + 6f;
					}
				}
				((Vector2)(ref val))..ctor(placement.anchor_x, placement.anchor_y);
				row.rect.anchorMin = val;
				row.rect.anchorMax = val;
				row.rect.pivot = val;
				row.rect.anchoredPosition = new Vector2(placement.row_x(), placement.row_y(num));
				TextMeshProUGUI text = row.text;
				float anchor_x = placement.anchor_x;
				TextAlignmentOptions alignment = ((anchor_x == 0f) ? ((TextAlignmentOptions)513) : ((anchor_x != 1f) ? ((TextAlignmentOptions)514) : ((TextAlignmentOptions)516)));
				((TMP_Text)text).alignment = alignment;
			}
		}

		private static void build()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00dc: 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_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			canvas_object = new GameObject("DeathNoticesFeed");
			Canvas obj = canvas_object.AddComponent<Canvas>();
			obj.renderMode = (RenderMode)0;
			obj.sortingOrder = 30000;
			CanvasScaler obj2 = canvas_object.AddComponent<CanvasScaler>();
			obj2.uiScaleMode = (ScaleMode)1;
			obj2.referenceResolution = new Vector2(1920f, 1080f);
			obj2.matchWidthOrHeight = 1f;
			TMP_FontAsset val = null;
			if (Singleton<NotificationsManager>.InstanceExists && (Object)(object)Singleton<NotificationsManager>.Instance.NotificationPrefab != (Object)null)
			{
				TextMeshProUGUI componentInChildren = Singleton<NotificationsManager>.Instance.NotificationPrefab.GetComponentInChildren<TextMeshProUGUI>(true);
				val = ((componentInChildren != null) ? ((TMP_Text)componentInChildren).font : null);
			}
			for (int i = 0; i < rows.Length; i++)
			{
				GameObject val2 = new GameObject("Notice");
				val2.transform.SetParent(canvas_object.transform, false);
				RectTransform rect = val2.AddComponent<RectTransform>();
				Image obj3 = val2.AddComponent<Image>();
				((Graphic)obj3).color = new Color(0.05f, 0.05f, 0.07f, 0.82f);
				((Graphic)obj3).raycastTarget = false;
				CanvasGroup val3 = val2.AddComponent<CanvasGroup>();
				val3.blocksRaycasts = false;
				val3.interactable = false;
				GameObject val4 = new GameObject("Text");
				val4.transform.SetParent(val2.transform, false);
				TextMeshProUGUI val5 = val4.AddComponent<TextMeshProUGUI>();
				if ((Object)(object)val != (Object)null)
				{
					((TMP_Text)val5).font = val;
				}
				((TMP_Text)val5).fontSize = 22f;
				((Graphic)val5).color = Color.white;
				((TMP_Text)val5).enableWordWrapping = true;
				((TMP_Text)val5).overflowMode = (TextOverflowModes)0;
				((Graphic)val5).raycastTarget = false;
				RectTransform rectTransform = ((TMP_Text)val5).rectTransform;
				rectTransform.anchorMin = Vector2.zero;
				rectTransform.anchorMax = Vector2.one;
				rectTransform.offsetMin = new Vector2(18f, 9f);
				rectTransform.offsetMax = new Vector2(-18f, -9f);
				val2.SetActive(false);
				rows[i] = new Row(val2, rect, val3, val5);
			}
		}
	}
	internal enum NoticePosition
	{
		TopLeft,
		TopCenter,
		TopRight,
		BottomLeft,
		BottomCenter,
		BottomRight
	}
	internal readonly record struct NoticePlacement(NoticePosition position = NoticePosition.TopCenter, float margin_x = 24f, float margin_y = 72f)
	{
		public bool is_top
		{
			get
			{
				NoticePosition noticePosition = position;
				if ((uint)noticePosition <= 2u)
				{
					return true;
				}
				return false;
			}
		}

		public float anchor_x
		{
			get
			{
				switch (position)
				{
				case NoticePosition.TopLeft:
				case NoticePosition.BottomLeft:
					return 0f;
				case NoticePosition.TopRight:
				case NoticePosition.BottomRight:
					return 1f;
				default:
					return 0.5f;
				}
			}
		}

		public float anchor_y
		{
			get
			{
				if (!is_top)
				{
					return 0f;
				}
				return 1f;
			}
		}

		public NoticePlacement validated()
		{
			return this with
			{
				position = ((!Enum.IsDefined(position)) ? NoticePosition.TopCenter : position),
				margin_x = (float.IsFinite(margin_x) ? Math.Clamp(margin_x, 0f, 600f) : 24f),
				margin_y = (float.IsFinite(margin_y) ? Math.Clamp(margin_y, 0f, 900f) : 72f)
			};
		}

		public float row_x()
		{
			if (anchor_x == 0f)
			{
				return margin_x;
			}
			if (anchor_x == 1f)
			{
				return 0f - margin_x;
			}
			return 0f;
		}

		public float row_y(float stack_offset)
		{
			if (!float.IsFinite(stack_offset) || stack_offset < 0f)
			{
				throw new ArgumentOutOfRangeException("stack_offset");
			}
			float num = margin_y + stack_offset;
			if (!is_top)
			{
				return num;
			}
			return 0f - num;
		}
	}
	internal sealed class NoticeQueue
	{
		private readonly (string message, float expires_seconds, string title)[] entries = new(string, float, string)[16];

		private int read_index;

		private int count;

		public void reset()
		{
			Array.Clear(entries, 0, entries.Length);
			read_index = 0;
			count = 0;
		}

		public void add(string message, float now_seconds, string title)
		{
			if (!string.IsNullOrEmpty(message) && message.Length <= 192 && !string.IsNullOrEmpty(title) && title.Length <= 32 && float.IsFinite(now_seconds))
			{
				if (count == entries.Length)
				{
					read_index = (read_index + 1) % entries.Length;
					count--;
				}
				entries[(read_index + count) % entries.Length] = (message: message, expires_seconds: now_seconds + 10f, title: title);
				count++;
			}
		}

		public bool try_take(float now_seconds, out string message, out string title)
		{
			message = "";
			title = "";
			if (!float.IsFinite(now_seconds))
			{
				return false;
			}
			for (int i = 0; i < entries.Length; i++)
			{
				if (count <= 0)
				{
					break;
				}
				(string, float, string) tuple = entries[read_index];
				entries[read_index] = default((string, float, string));
				read_index = (read_index + 1) % entries.Length;
				count--;
				if (!(now_seconds > tuple.Item2))
				{
					(message, _, title) = tuple;
					return true;
				}
			}
			return false;
		}
	}
}