Decompiled source of Noted Not Approved v0.1.1

BepInEx/plugins/NotedNotApproved/NotedNotApproved.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Realtime;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("Vlad Alive")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+6ea067a38bbdbc0b7c09091b4ff3f0defa6a3640")]
[assembly: AssemblyProduct("NotedNotApproved")]
[assembly: AssemblyTitle("NotedNotApproved")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.1.0")]
[module: UnverifiableCode]
[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.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]
	[Microsoft.CodeAnalysis.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;
		}
	}
	[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 NotedNotApproved
{
	internal static class NotedBroadcast
	{
		public static bool TryBroadcast(string message)
		{
			if (!NotedNotApprovedPlugin.BroadcastEnabled)
			{
				NotedNotApprovedPlugin.LogVerbose("Broadcast skipped: disabled by config.");
				return false;
			}
			if (string.IsNullOrWhiteSpace(message))
			{
				NotedNotApprovedPlugin.LogVerbose("Broadcast skipped: empty message.");
				return false;
			}
			try
			{
				ChatManager instance = ChatManager.instance;
				if (instance == null)
				{
					NotedNotApprovedPlugin.LogVerbose("Broadcast skipped: ChatManager.instance is null.");
					return false;
				}
				instance.ForceSendMessage(message);
				NotedNotApprovedPlugin.LogVerbose("Broadcast sent through ChatManager.ForceSendMessage.");
				return true;
			}
			catch (Exception ex)
			{
				NotedNotApprovedPlugin.Logger.LogWarning((object)("Broadcast failed: " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}
	}
	[BepInPlugin("VladAlive.NotedNotApproved", "Noted, Not Approved", "0.1.1")]
	public sealed class NotedNotApprovedPlugin : BaseUnityPlugin
	{
		private readonly NotedRoundTracker _tracker = new NotedRoundTracker();

		private Harmony? _harmony;

		internal static NotedNotApprovedPlugin Instance { get; private set; }

		internal static ManualLogSource Logger => Instance._logger;

		internal ConfigEntry<bool> EnableReports { get; private set; }

		internal ConfigEntry<bool> ShowOverlay { get; private set; }

		internal ConfigEntry<bool> BroadcastReports { get; private set; }

		internal ConfigEntry<bool> DevSnapshotReports { get; private set; }

		internal ConfigEntry<string> Language { get; private set; }

		internal ConfigEntry<bool> VerboseLogging { get; private set; }

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal static NotedRoundTracker Tracker => Instance._tracker;

		internal static bool ReportsEnabled => Instance.EnableReports.Value;

		internal static bool OverlayEnabled => Instance.ShowOverlay.Value;

		internal static bool BroadcastEnabled => Instance.BroadcastReports.Value;

		internal static bool SnapshotReportsEnabled => Instance.DevSnapshotReports.Value;

		private void Awake()
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Expected O, but got Unknown
			Instance = this;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
			EnableReports = ((BaseUnityPlugin)this).Config.Bind<bool>("Reports", "EnableReports", true, "Generate reports after completed rounds.");
			ShowOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Reports", "ShowOverlay", true, "Show the latest report on the host screen.");
			BroadcastReports = ((BaseUnityPlugin)this).Config.Bind<bool>("Reports", "BroadcastReports", true, "Send a compact report through a vanilla-visible message path when possible.");
			DevSnapshotReports = ((BaseUnityPlugin)this).Config.Bind<bool>("Development", "DevSnapshotReports", true, "Enable F8 host-only snapshot reports during an active run.");
			Language = ((BaseUnityPlugin)this).Config.Bind<string>("Reports", "Language", "en", "Internal language code for report text.");
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log product lifecycle details.");
			t.Configure(Language.Value);
			_harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
			_harmony.PatchAll();
			NotedSteamManagerPatches.InstallOptionalDisconnectPatch(_harmony);
			((Component)this).gameObject.AddComponent<NotedOverlay>();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} loaded.");
		}

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

		internal static void LogVerbose(string message)
		{
			if (Instance.VerboseLogging.Value)
			{
				Logger.LogInfo((object)message);
			}
		}
	}
	public sealed class NotedOverlay : MonoBehaviour
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static WindowFunction <0>__DrawWindow;
		}

		private const float WindowWidth = 460f;

		private const float WindowHeight = 320f;

		private const float WindowMargin = 24f;

		private const float HeaderButtonWidth = 32f;

		private const float WindowTopOffset = 344f;

		private static string? LatestReport { get; set; }

		private static bool Visible { get; set; }

		private static Rect WindowRect { get; set; } = new Rect(24f, 24f, 460f, 320f);


		private static Vector2 Scroll { get; set; }

		public static void Show(string report)
		{
			if (NotedNotApprovedPlugin.OverlayEnabled)
			{
				LatestReport = report;
				Visible = true;
				MoveToTopRight();
				NotedNotApprovedPlugin.LogVerbose($"Showing Noted overlay. reportLength={report.Length}");
			}
		}

		private void Update()
		{
			if (Visible && Input.GetKeyDown((KeyCode)27))
			{
				NotedNotApprovedPlugin.LogVerbose("Dismissing Noted overlay with Escape.");
				Dismiss();
				return;
			}
			if (Input.GetKeyDown((KeyCode)110))
			{
				NotedNotApprovedPlugin.LogVerbose("Reopening Noted overlay with N.");
				Reopen();
			}
			if (NotedNotApprovedPlugin.SnapshotReportsEnabled && Input.GetKeyDown((KeyCode)289))
			{
				ToggleSnapshot();
			}
		}

		private static void ToggleSnapshot()
		{
			if (Visible)
			{
				NotedNotApprovedPlugin.LogVerbose("Dismissing Noted overlay with F8.");
				Dismiss();
				return;
			}
			NotedRoundFacts snapshot = NotedNotApprovedPlugin.Tracker.GetSnapshot();
			if (snapshot == null)
			{
				NotedNotApprovedPlugin.LogVerbose("Snapshot requested but no active Noted round exists.");
				return;
			}
			string report = NotedReportGenerator.GenerateSnapshot(snapshot);
			Show(report);
		}

		private static void Reopen()
		{
			if (!string.IsNullOrWhiteSpace(LatestReport))
			{
				Visible = true;
				MoveToTopRight();
			}
		}

		private static void Dismiss()
		{
			Visible = false;
			NotedNotApprovedPlugin.LogVerbose("Dismissed Noted overlay.");
		}

		private void OnGUI()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			if (Visible && !string.IsNullOrWhiteSpace(LatestReport))
			{
				GUI.depth = -1000;
				KeepWindowOnScreen();
				int instanceID = ((Object)this).GetInstanceID();
				Rect windowRect = WindowRect;
				object obj = <>O.<0>__DrawWindow;
				if (obj == null)
				{
					WindowFunction val = DrawWindow;
					<>O.<0>__DrawWindow = val;
					obj = (object)val;
				}
				WindowRect = GUI.Window(instanceID, windowRect, (WindowFunction)obj, t.Get("overlay.title"));
			}
		}

		private static void DrawWindow(int windowId)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			Scroll = GUILayout.BeginScrollView(Scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(232f) });
			GUILayout.Label(LatestReport, Array.Empty<GUILayoutOption>());
			GUILayout.EndScrollView();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(t.Get("overlay.keyboard_hint"), Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button(t.Get("overlay.dismiss"), (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(120f),
				GUILayout.Height(32f)
			}))
			{
				Dismiss();
			}
			GUILayout.EndHorizontal();
			GUILayout.EndVertical();
			GUI.DragWindow();
		}

		private static void MoveToTopRight()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			WindowRect = new Rect(Mathf.Max(24f, (float)Screen.width - 460f - 24f), 344f, 460f, 320f);
		}

		private static void KeepWindowOnScreen()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			float num = Screen.width;
			Rect windowRect = WindowRect;
			float num2 = Mathf.Max(24f, num - ((Rect)(ref windowRect)).width - 24f);
			float num3 = Screen.height;
			windowRect = WindowRect;
			float num4 = Mathf.Max(24f, num3 - ((Rect)(ref windowRect)).height - 24f);
			Rect windowRect2 = WindowRect;
			((Rect)(ref windowRect2)).x = Mathf.Clamp(((Rect)(ref windowRect2)).x, 24f, num2);
			((Rect)(ref windowRect2)).y = Mathf.Clamp(((Rect)(ref windowRect2)).y, 24f, num4);
			WindowRect = windowRect2;
		}
	}
	public static class NotedReportGenerator
	{
		public static string Generate(NotedRoundFacts facts)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(t.Get("report.title"));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(t.Get("report.section.worked"));
			stringBuilder.AppendLine(Bullet(BuildMoneyLine(facts)));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(t.Get("report.section.concerns"));
			stringBuilder.AppendLine(Bullet(BuildDeathLine(facts)));
			stringBuilder.AppendLine(Bullet(BuildPlayerLeftLine(facts)));
			stringBuilder.AppendLine(Bullet(BuildLossLine(facts)));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(t.Get("report.section.management"));
			stringBuilder.AppendLine(Bullet(t.Get("report.management.receipt_policy")));
			return stringBuilder.ToString().TrimEnd();
		}

		public static string GenerateSnapshot(NotedRoundFacts facts)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(t.Get("snapshot.title"));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(t.Get("report.section.worked"));
			stringBuilder.AppendLine(Bullet(BuildMoneyLine(facts)));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(t.Get("report.section.concerns"));
			stringBuilder.AppendLine(Bullet(BuildDeathLine(facts)));
			stringBuilder.AppendLine(Bullet(BuildPlayerLeftLine(facts)));
			stringBuilder.AppendLine(Bullet(BuildLossLine(facts)));
			return stringBuilder.ToString().TrimEnd();
		}

		public static string GenerateBroadcast(NotedRoundFacts facts)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(t.Get("broadcast.title"));
			stringBuilder.AppendLine(BuildBroadcastMoneyLine(facts));
			stringBuilder.AppendLine(BuildBroadcastDeathLine(facts));
			stringBuilder.AppendLine(BuildBroadcastPlayerLeftLine(facts));
			stringBuilder.AppendLine(BuildBroadcastLossLine(facts));
			return stringBuilder.ToString().TrimEnd();
		}

		private static string BuildMoneyLine(NotedRoundFacts facts)
		{
			int valueOrDefault = facts.ExtractedHaul.GetValueOrDefault();
			int valueOrDefault2 = facts.Quota.GetValueOrDefault();
			if (facts.RoundSucceeded.GetValueOrDefault())
			{
				return t.Get("report.money.success", ("extracted", valueOrDefault), ("quota", valueOrDefault2));
			}
			return t.Get("report.money.failure", ("extracted", valueOrDefault), ("quota", valueOrDefault2));
		}

		private static string Bullet(string text)
		{
			return "- " + text;
		}

		private static string BuildDeathLine(NotedRoundFacts facts)
		{
			if (facts.Deaths.Count == 0)
			{
				return t.Get("report.death.none");
			}
			NotedDeathFact notedDeathFact = facts.Deaths.OrderBy((NotedDeathFact death) => death.OccurredAtSeconds).First();
			return t.Get("report.death.first", ("player", notedDeathFact.PlayerName), ("seconds", notedDeathFact.OccurredAtSeconds.ToString("0")));
		}

		private static string BuildPlayerLeftLine(NotedRoundFacts facts)
		{
			if (facts.PlayerLeftEvents.Count == 0)
			{
				return t.Get("report.player_left.none");
			}
			NotedPlayerLeftFact notedPlayerLeftFact = facts.PlayerLeftEvents.OrderBy((NotedPlayerLeftFact left) => left.OccurredAtSeconds).First();
			return t.Get("report.player_left.first", ("player", notedPlayerLeftFact.PlayerName), ("seconds", notedPlayerLeftFact.OccurredAtSeconds.ToString("0")));
		}

		private static string BuildLossLine(NotedRoundFacts facts)
		{
			NotedValueLossFact notedValueLossFact = (from loss in facts.ValueLosses
				where loss.Confidence == NotedConfidence.High && !string.IsNullOrWhiteSpace(loss.DamagePlayerName)
				orderby loss.ValueLost descending
				select loss).FirstOrDefault();
			if (notedValueLossFact != null)
			{
				return t.Get("report.damage.named", ("player", notedValueLossFact.DamagePlayerName), ("value", notedValueLossFact.ValueLost));
			}
			int num = facts.ValueLosses.Sum((NotedValueLossFact loss) => loss.ValueLost);
			if (num > 0)
			{
				return t.Get("report.damage.team", ("value", num));
			}
			return t.Get("report.damage.none");
		}

		private static string BuildBroadcastMoneyLine(NotedRoundFacts facts)
		{
			string item = (string.IsNullOrWhiteSpace(facts.LevelName) ? t.Get("broadcast.level_fallback") : facts.LevelName);
			int valueOrDefault = facts.ExtractedHaul.GetValueOrDefault();
			int valueOrDefault2 = facts.Quota.GetValueOrDefault();
			return t.Get("broadcast.money", ("level", item), ("extracted", valueOrDefault), ("quota", valueOrDefault2));
		}

		private static string BuildBroadcastDeathLine(NotedRoundFacts facts)
		{
			if (facts.Deaths.Count == 0)
			{
				return t.Get("broadcast.death.none");
			}
			NotedDeathFact notedDeathFact = facts.Deaths.OrderBy((NotedDeathFact death) => death.OccurredAtSeconds).First();
			return t.Get("broadcast.death.first", ("player", notedDeathFact.PlayerName), ("seconds", notedDeathFact.OccurredAtSeconds.ToString("0")));
		}

		private static string BuildBroadcastPlayerLeftLine(NotedRoundFacts facts)
		{
			if (facts.PlayerLeftEvents.Count == 0)
			{
				return t.Get("broadcast.player_left.none");
			}
			NotedPlayerLeftFact notedPlayerLeftFact = facts.PlayerLeftEvents.OrderBy((NotedPlayerLeftFact left) => left.OccurredAtSeconds).First();
			return t.Get("broadcast.player_left.first", ("player", notedPlayerLeftFact.PlayerName), ("seconds", notedPlayerLeftFact.OccurredAtSeconds.ToString("0")));
		}

		private static string BuildBroadcastLossLine(NotedRoundFacts facts)
		{
			NotedValueLossFact notedValueLossFact = (from loss in facts.ValueLosses
				where loss.Confidence == NotedConfidence.High && !string.IsNullOrWhiteSpace(loss.DamagePlayerName)
				orderby loss.ValueLost descending
				select loss).FirstOrDefault();
			if (notedValueLossFact != null)
			{
				return t.Get("broadcast.damage.named", ("player", notedValueLossFact.DamagePlayerName), ("value", notedValueLossFact.ValueLost));
			}
			int num = facts.ValueLosses.Sum((NotedValueLossFact loss) => loss.ValueLost);
			if (num > 0)
			{
				return t.Get("broadcast.damage.team", ("value", num));
			}
			return t.Get("broadcast.damage.none");
		}
	}
	public enum NotedConfidence
	{
		Low,
		Medium,
		High
	}
	public sealed class NotedRoundFacts
	{
		public string LevelName { get; }

		public float RoundStartedAt { get; }

		public int? Quota { get; private set; }

		public int? ExtractedHaul { get; private set; }

		public bool? RoundSucceeded { get; private set; }

		public float? RoundEndedAt { get; private set; }

		public List<NotedDeathFact> Deaths { get; } = new List<NotedDeathFact>();


		public List<NotedValueLossFact> ValueLosses { get; } = new List<NotedValueLossFact>();


		public List<NotedPlayerLeftFact> PlayerLeftEvents { get; } = new List<NotedPlayerLeftFact>();


		public NotedRoundFacts(string levelName, float roundStartedAt)
		{
			LevelName = levelName;
			RoundStartedAt = roundStartedAt;
		}

		public float SecondsSinceRoundStart(float eventTime)
		{
			return Math.Max(0f, eventTime - RoundStartedAt);
		}

		public void RecordExtraction(int quota, int extractedHaul, bool roundSucceeded, float eventTime)
		{
			Quota = quota;
			ExtractedHaul = extractedHaul;
			RoundSucceeded = roundSucceeded;
			RoundEndedAt = eventTime;
		}

		public void RecordDeath(string playerName, bool isLocal, int health, int maxHealth, float eventTime)
		{
			Deaths.Add(new NotedDeathFact(playerName, isLocal, health, maxHealth, SecondsSinceRoundStart(eventTime)));
		}

		public void RecordPlayerLeft(string playerName, float eventTime)
		{
			if (!string.IsNullOrWhiteSpace(playerName))
			{
				PlayerLeftEvents.Add(new NotedPlayerLeftFact(playerName, SecondsSinceRoundStart(eventTime)));
			}
		}

		public void RecordValueLoss(int valueLost, int dollarOriginal, int dollarCurrent, string? damagePlayerName, NotedConfidence confidence, float eventTime)
		{
			if (valueLost > 0)
			{
				ValueLosses.Add(new NotedValueLossFact(valueLost, dollarOriginal, dollarCurrent, damagePlayerName, confidence, SecondsSinceRoundStart(eventTime)));
			}
		}
	}
	public sealed class NotedDeathFact
	{
		public string PlayerName { get; }

		public bool IsLocal { get; }

		public int Health { get; }

		public int MaxHealth { get; }

		public float OccurredAtSeconds { get; }

		public NotedDeathFact(string playerName, bool isLocal, int health, int maxHealth, float occurredAtSeconds)
		{
			PlayerName = playerName;
			IsLocal = isLocal;
			Health = health;
			MaxHealth = maxHealth;
			OccurredAtSeconds = occurredAtSeconds;
		}
	}
	public sealed class NotedValueLossFact
	{
		public int ValueLost { get; }

		public int DollarOriginal { get; }

		public int DollarCurrent { get; }

		public string? DamagePlayerName { get; }

		public NotedConfidence Confidence { get; }

		public float OccurredAtSeconds { get; }

		public NotedValueLossFact(int valueLost, int dollarOriginal, int dollarCurrent, string? damagePlayerName, NotedConfidence confidence, float occurredAtSeconds)
		{
			ValueLost = valueLost;
			DollarOriginal = dollarOriginal;
			DollarCurrent = dollarCurrent;
			DamagePlayerName = damagePlayerName;
			Confidence = confidence;
			OccurredAtSeconds = occurredAtSeconds;
		}
	}
	public sealed class NotedPlayerLeftFact
	{
		public string PlayerName { get; }

		public float OccurredAtSeconds { get; }

		public NotedPlayerLeftFact(string playerName, float occurredAtSeconds)
		{
			PlayerName = playerName;
			OccurredAtSeconds = occurredAtSeconds;
		}
	}
	public sealed class NotedRoundTracker
	{
		private sealed class RecentValueLoss
		{
			public int ValueLost { get; }

			public int DollarOriginal { get; }

			public int DollarCurrent { get; }

			public float EventTime { get; }

			public RecentValueLoss(int valueLost, int dollarOriginal, int dollarCurrent, float eventTime)
			{
				ValueLost = valueLost;
				DollarOriginal = dollarOriginal;
				DollarCurrent = dollarCurrent;
				EventTime = eventTime;
			}
		}

		private const float duplicateStartWindowSeconds = 2f;

		private const float recentValueLossWindowSeconds = 0.25f;

		private static readonly HashSet<string> NonRunLevels = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			"Splash_Screen", "Main_Menu", "Lobby_Menu", "Service_Station", "Truck", "Disposal_Arena", "Splash Screen", "Main Menu", "Lobby Menu", "Service Station",
			"Disposal Arena"
		};

		private readonly List<RecentValueLoss> _recentValueLosses = new List<RecentValueLoss>();

		public NotedRoundFacts? ActiveRound { get; private set; }

		public NotedRoundFacts? LastCompletedRound { get; private set; }

		public string? LastStartedLevelName { get; private set; }

		private float LastStartedAt { get; set; } = -999f;


		public static bool IsReportableRunLevel(string? levelName, bool levelIsShop)
		{
			if (string.IsNullOrWhiteSpace(levelName) || levelIsShop)
			{
				return false;
			}
			return !NonRunLevels.Contains(levelName.Trim());
		}

		public bool StartRound(string levelName, bool levelIsShop, float now)
		{
			if (!IsReportableRunLevel(levelName, levelIsShop))
			{
				return false;
			}
			if (LastStartedLevelName == levelName && now - LastStartedAt <= 2f)
			{
				return false;
			}
			ActiveRound = new NotedRoundFacts(levelName, now);
			LastStartedLevelName = levelName;
			LastStartedAt = now;
			NotedNotApprovedPlugin.LogVerbose("Started Noted round for " + levelName + ".");
			return true;
		}

		public NotedRoundFacts? TryCompleteRound(int quota, int extractedHaul, bool succeeded, float now)
		{
			if (ActiveRound == null)
			{
				return null;
			}
			ActiveRound.RecordExtraction(quota, extractedHaul, succeeded, now);
			LastCompletedRound = ActiveRound;
			ActiveRound = null;
			return LastCompletedRound;
		}

		public NotedRoundFacts? GetSnapshot()
		{
			return ActiveRound;
		}

		public void RecordDeath(string playerName, bool isLocal, int health, int maxHealth, float now)
		{
			ActiveRound?.RecordDeath(playerName, isLocal, health, maxHealth, now);
		}

		public void RecordPlayerLeft(string playerName, float now)
		{
			ActiveRound?.RecordPlayerLeft(playerName, now);
		}

		public void RecordValueLoss(int valueLost, int dollarOriginal, int dollarCurrent, string? playerName, NotedConfidence confidence, float now)
		{
			if (!IsDuplicateValueLoss(valueLost, dollarOriginal, dollarCurrent, now))
			{
				ActiveRound?.RecordValueLoss(valueLost, dollarOriginal, dollarCurrent, playerName, confidence, now);
			}
		}

		private bool IsDuplicateValueLoss(int valueLost, int dollarOriginal, int dollarCurrent, float now)
		{
			_recentValueLosses.RemoveAll((RecentValueLoss loss) => now - loss.EventTime > 0.25f);
			foreach (RecentValueLoss recentValueLoss in _recentValueLosses)
			{
				if (recentValueLoss.ValueLost == valueLost && recentValueLoss.DollarOriginal == dollarOriginal && recentValueLoss.DollarCurrent == dollarCurrent)
				{
					return true;
				}
			}
			_recentValueLosses.Add(new RecentValueLoss(valueLost, dollarOriginal, dollarCurrent, now));
			return false;
		}
	}
	[HarmonyPatch(typeof(RoundDirector))]
	internal static class NotedRoundDirectorPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("StartRoundLogic")]
		private static void StartRoundLogic_Postfix()
		{
			if (NotedNotApprovedPlugin.ReportsEnabled)
			{
				RunManager instance = RunManager.instance;
				string levelName = instance?.levelCurrent?.NarrativeName ?? string.Empty;
				bool levelIsShop = instance?.levelIsShop ?? false;
				NotedNotApprovedPlugin.Tracker.StartRound(levelName, levelIsShop, Time.time);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("ExtractionCompletedAllCheck")]
		private static void ExtractionCompletedAllCheck_Postfix()
		{
		}

		[HarmonyPostfix]
		[HarmonyPatch("ExtractionCompleted")]
		private static void ExtractionCompleted_Postfix(RoundDirector __instance)
		{
			if (NotedNotApprovedPlugin.ReportsEnabled)
			{
				int haulGoal = __instance.haulGoal;
				int totalHaul = __instance.totalHaul;
				bool succeeded = totalHaul >= haulGoal;
				NotedRoundFacts facts = NotedNotApprovedPlugin.Tracker.TryCompleteRound(haulGoal, totalHaul, succeeded, Time.time);
				ReportRound(facts);
			}
		}

		internal static void ReportRound(NotedRoundFacts? facts)
		{
			if (facts == null)
			{
				NotedNotApprovedPlugin.Logger.LogInfo((object)"No active Noted round to report.");
				return;
			}
			NotedNotApprovedPlugin.Logger.LogInfo((object)("Showing report for " + facts.LevelName + "."));
			string report = NotedReportGenerator.Generate(facts);
			NotedOverlay.Show(report);
			string message = NotedReportGenerator.GenerateBroadcast(facts);
			NotedBroadcast.TryBroadcast(message);
		}
	}
	[HarmonyPatch(typeof(RunManager))]
	internal static class NotedRunManagerPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("ChangeLevel")]
		private static void ChangeLevel_Postfix(bool _levelFailed)
		{
			if (NotedNotApprovedPlugin.ReportsEnabled && _levelFailed)
			{
				RoundDirector instance = RoundDirector.instance;
				int quota = instance?.haulGoal ?? 0;
				int extractedHaul = instance?.totalHaul ?? 0;
				NotedRoundFacts facts = NotedNotApprovedPlugin.Tracker.TryCompleteRound(quota, extractedHaul, succeeded: false, Time.time);
				NotedRoundDirectorPatches.ReportRound(facts);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar))]
	internal static class NotedPlayerAvatarPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("PlayerDeathDone")]
		private static void PlayerDeathDone_Postfix(PlayerAvatar __instance)
		{
			if (NotedNotApprovedPlugin.ReportsEnabled)
			{
				int health = __instance.playerHealth?.health ?? 0;
				int maxHealth = __instance.playerHealth?.maxHealth ?? 0;
				NotedNotApprovedPlugin.Tracker.RecordDeath(__instance.playerName ?? "unknown", __instance.isLocal, health, maxHealth, Time.time);
			}
		}
	}
	internal static class NotedSteamManagerPatches
	{
		internal static void InstallOptionalDisconnectPatch(Harmony harmony)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(SteamManager), "OnPlayerLeftRoom", new Type[1] { typeof(Player) }, (Type[])null);
			if ((object)methodInfo == null)
			{
				NotedNotApprovedPlugin.Logger.LogWarning((object)"Skipping optional Noted disconnect hook; SteamManager.OnPlayerLeftRoom was not found.");
				return;
			}
			HarmonyMethod val = new HarmonyMethod(typeof(NotedSteamManagerPatches), "OnPlayerLeftRoom_Postfix", (Type[])null);
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void OnPlayerLeftRoom_Postfix(Player otherPlayer)
		{
			if (NotedNotApprovedPlugin.ReportsEnabled)
			{
				string text = ((otherPlayer != null) ? otherPlayer.NickName : null) ?? "unknown";
				NotedNotApprovedPlugin.Tracker.RecordPlayerLeft(text, Time.time);
				NotedNotApprovedPlugin.Logger.LogInfo((object)("Recorded player left during Noted round: " + text + "."));
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabObjectImpactDetector))]
	internal static class NotedPhysGrabObjectImpactDetectorPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("Break")]
		private static void Break_Postfix(PhysGrabObjectImpactDetector __instance, float valueLost)
		{
			RecordBreak(__instance, valueLost);
		}

		[HarmonyPostfix]
		[HarmonyPatch("BreakRPC")]
		private static void BreakRPC_Postfix(PhysGrabObjectImpactDetector __instance, float valueLost)
		{
			RecordBreak(__instance, valueLost);
		}

		private static void RecordBreak(PhysGrabObjectImpactDetector detector, float valueLost)
		{
			if (!NotedNotApprovedPlugin.ReportsEnabled)
			{
				return;
			}
			PhysGrabObject physGrabObject = detector.physGrabObject;
			if (!(valueLost <= 0f) && (physGrabObject == null || !physGrabObject.isPlayer))
			{
				ValuableObject val = ((physGrabObject != null) ? ((Component)physGrabObject).GetComponent<ValuableObject>() : null);
				if (val != null)
				{
					string text = physGrabObject?.lastPlayerGrabbing?.playerName;
					NotedConfidence confidence = ((!string.IsNullOrWhiteSpace(text)) ? NotedConfidence.High : NotedConfidence.Low);
					NotedNotApprovedPlugin.Tracker.RecordValueLoss(Mathf.RoundToInt(valueLost), Mathf.RoundToInt(val.dollarValueOriginal), Mathf.RoundToInt(val.dollarValueCurrent), text, confidence, Time.time);
				}
			}
		}
	}
	internal static class t
	{
		internal const string FallbackLanguage = "en";

		private static readonly Dictionary<string, Dictionary<string, string>> Catalogs = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> WarnedMissingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static string CurrentLanguage { get; set; } = "en";


		public static void Configure(string language)
		{
			CurrentLanguage = NormalizeLanguage(language);
			LoadCatalog("en");
			LoadCatalog(CurrentLanguage);
		}

		public static string Get(string key, params (string Key, object? Value)[] values)
		{
			string text = Lookup(CurrentLanguage, key);
			if (text == null && !string.Equals(CurrentLanguage, "en", StringComparison.OrdinalIgnoreCase))
			{
				WarnUntranslated(CurrentLanguage, key);
				text = Lookup("en", key);
			}
			if (text == null)
			{
				WarnUntranslated("en", key);
				text = key;
			}
			for (int i = 0; i < values.Length; i++)
			{
				(string Key, object? Value) tuple = values[i];
				string item = tuple.Key;
				object item2 = tuple.Value;
				text = text.Replace("{" + item + "}", Convert.ToString(item2, CultureInfo.InvariantCulture));
			}
			return text;
		}

		private static string NormalizeLanguage(string? language)
		{
			if (string.IsNullOrWhiteSpace(language))
			{
				return "en";
			}
			return language.Trim().Split('-', '_')[0].ToLowerInvariant();
		}

		private static string? Lookup(string language, string key)
		{
			LoadCatalog(language);
			if (!Catalogs.TryGetValue(language, out Dictionary<string, string> value) || !value.TryGetValue(key, out var value2))
			{
				return null;
			}
			return value2;
		}

		private static void LoadCatalog(string language)
		{
			if (!Catalogs.ContainsKey(language))
			{
				Catalogs[language] = ParseCatalog(ReadCatalog(language));
			}
		}

		private static string ReadCatalog(string language)
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			string value = ".i18n." + language + ".yml";
			string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
			foreach (string text in manifestResourceNames)
			{
				if (!text.EndsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				using Stream stream = executingAssembly.GetManifestResourceStream(text);
				if (stream == null)
				{
					break;
				}
				using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
				return streamReader.ReadToEnd();
			}
			NotedNotApprovedPlugin.Logger.LogWarning((object)("i18n catalog missing for language '" + language + "'."));
			return string.Empty;
		}

		private static Dictionary<string, string> ParseCatalog(string yaml)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			string[] array = yaml.Split('\n');
			foreach (string text in array)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				int num = text.Length - text.TrimStart(' ').Length;
				int val = num / 2;
				string text2 = text.Trim();
				if (string.IsNullOrWhiteSpace(text2) || text2.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				int num2 = text2.IndexOf(':');
				if (num2 > 0)
				{
					string text3 = text2.Substring(0, num2).Trim();
					string text4 = text2;
					int num3 = num2 + 1;
					string value = text4.Substring(num3, text4.Length - num3).Trim();
					list.RemoveRange(Math.Min(val, list.Count), list.Count - Math.Min(val, list.Count));
					if (string.IsNullOrWhiteSpace(value))
					{
						list.Add(text3);
						continue;
					}
					dictionary[string.Join(".", list.Concat(new string[1] { text3 }))] = Unquote(value);
				}
			}
			return dictionary;
		}

		private static string Unquote(string value)
		{
			if (value.Length >= 2 && value[0] == '"')
			{
				if (value[value.Length - 1] == '"')
				{
					return value.Substring(1, value.Length - 1 - 1).Replace("\\\"", "\"");
				}
			}
			return value;
		}

		private static void WarnUntranslated(string language, string key)
		{
			string item = language + ":" + key;
			if (WarnedMissingKeys.Add(item))
			{
				NotedNotApprovedPlugin.Logger.LogWarning((object)("i18n untranslated key '" + key + "' for language '" + language + "', falling back to English."));
			}
		}
	}
}