Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of DeathNotices v0.5.0
Mods/DeathNotices.dll
Decompiled 3 days agousing 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; } } }