Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Noted Not Approved v0.1.1
BepInEx/plugins/NotedNotApproved/NotedNotApproved.dll
Decompiled a month agousing 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.")); } } } }