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 EnableReports { get; private set; } internal ConfigEntry ShowOverlay { get; private set; } internal ConfigEntry BroadcastReports { get; private set; } internal ConfigEntry DevSnapshotReports { get; private set; } internal ConfigEntry Language { get; private set; } internal ConfigEntry 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("Reports", "EnableReports", true, "Generate reports after completed rounds."); ShowOverlay = ((BaseUnityPlugin)this).Config.Bind("Reports", "ShowOverlay", true, "Show the latest report on the host screen."); BroadcastReports = ((BaseUnityPlugin)this).Config.Bind("Reports", "BroadcastReports", true, "Send a compact report through a vanilla-visible message path when possible."); DevSnapshotReports = ((BaseUnityPlugin)this).Config.Bind("Development", "DevSnapshotReports", true, "Enable F8 host-only snapshot reports during an active run."); Language = ((BaseUnityPlugin)this).Config.Bind("Reports", "Language", "en", "Internal language code for report text."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("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(); 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()); Scroll = GUILayout.BeginScrollView(Scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(232f) }); GUILayout.Label(LatestReport, Array.Empty()); GUILayout.EndScrollView(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(t.Get("overlay.keyboard_hint"), Array.Empty()); 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 Deaths { get; } = new List(); public List ValueLosses { get; } = new List(); public List PlayerLeftEvents { get; } = new List(); 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 NonRunLevels = new HashSet(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 _recentValueLosses = new List(); 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() : 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> Catalogs = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet WarnedMissingKeys = new HashSet(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 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 ParseCatalog(string yaml) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); List list = new List(); 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.")); } } } }