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 SimpleBlackBox v0.1.6
SimpleBlackBox.dll
Decompiled 12 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; 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.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using SimpleBlackBox.Core; using SimpleBlackBox.Features; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("ClientNetworkTransform")] [assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")] [assembly: IgnoresAccessChecksTo("DissonanceVoip")] [assembly: IgnoresAccessChecksTo("DunGen")] [assembly: IgnoresAccessChecksTo("DunGen.Integration.ASPP")] [assembly: IgnoresAccessChecksTo("DunGen.Integration.UnityNav")] [assembly: IgnoresAccessChecksTo("EasyTextEffects")] [assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")] [assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")] [assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")] [assembly: IgnoresAccessChecksTo("Unity.Burst")] [assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")] [assembly: IgnoresAccessChecksTo("Unity.Collections")] [assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")] [assembly: IgnoresAccessChecksTo("Unity.Jobs")] [assembly: IgnoresAccessChecksTo("Unity.Mathematics")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")] [assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")] [assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")] [assembly: IgnoresAccessChecksTo("Unity.Services.QoS")] [assembly: IgnoresAccessChecksTo("Unity.Services.Relay")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("Unity.Timeline")] [assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.XR.CoreUtils")] [assembly: IgnoresAccessChecksTo("Unity.XR.Management")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.ConformanceAutomation")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MetaQuestSupport")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MockRuntime")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.OculusQuestSupport")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.RuntimeDebugger")] [assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.SpatialTracking")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: IgnoresAccessChecksTo("UnityEngine.XR.LegacyInputHelpers")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 SimpleBlackBox { internal static class ChatUtil { internal static List<string> Capture; internal static void Send(string message, bool broadcast = false) { try { if (Capture != null && !broadcast) { Capture.Add(message.Replace("\ud83d\udee1 ", "")); return; } HUDManager instance = HUDManager.Instance; if (!((Object)(object)instance == (Object)null)) { string text = "<color=#ff9466>BlackBox</color>: <color=#e8e86a>" + message + "</color>"; if (broadcast) { instance.AddTextMessageServerRpc(text); } else { instance.AddChatMessage(text, "", -1, false); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ChatUtil.Send] {arg}"); } } } internal static class CommandRouter { internal static bool IsSenderTheLocalPlayer(int playerId) { try { if (playerId < 0) { return true; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return false; } return playerId == (int)val.playerClientId || playerId == (int)val.actualClientId; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CommandRouter.IsSenderTheLocalPlayer] {arg}"); return false; } } private static void HandleGodByName(string name, string toggle) { try { StartOfRound instance = StartOfRound.Instance; PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if (instance?.allPlayerScripts == null || (Object)(object)val == (Object)null) { ChatUtil.Send("No lobby."); return; } string value = name.Replace(" ", string.Empty).ToLowerInvariant(); List<PlayerControllerB> list = new List<PlayerControllerB>(); PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val2 in allPlayerScripts) { if (!((Object)(object)val2 == (Object)null) && val2.isPlayerControlled) { string text = (val2.playerUsername ?? "").Replace(" ", string.Empty).ToLowerInvariant(); if (text.Contains(value)) { list.Add(val2); } } } if (list.Count == 0) { ChatUtil.Send("No player matching \"" + name + "\"."); } else if (list.Count > 1) { List<string> list2 = new List<string>(); foreach (PlayerControllerB item in list) { list2.Add(item.playerUsername); } ChatUtil.Send("Ambiguous: " + string.Join(", ", list2) + ". Be more specific."); } else if ((Object)(object)list[0] == (Object)(object)val) { TestShield.GodMode = toggle == "on"; ChatUtil.Send(TestShield.GodMode ? "\ud83d\udee1 God mode ON for you (session only — resets on restart)." : "God mode OFF."); } else { ChatUtil.Send("Can't shield " + list[0].playerUsername + " from here — their deaths run on THEIR machine. Have them install SimpleBlackBox and type /bb god on themselves."); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[HandleGodByName] {arg}"); } } internal static bool Route(string raw) { try { if (string.IsNullOrWhiteSpace(raw)) { return false; } string[] array = raw.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string text = array[0].ToLowerInvariant(); int num; switch (text) { default: num = ((text == "delete") ? 1 : 0); break; case "bb": case "blackbox": case "log": case "move": num = 1; break; } bool flag = (byte)num != 0; if (flag && TournamentGate.Active) { ChatUtil.Send("Tournament mode: mod commands are off until the run ends."); return true; } if (flag && !TestingGate.Unlocked) { ChatUtil.Send("SimpleBlackBox is a testing tool: its commands are off while someone without SimpleBlackBox is in the lobby. They work again when you're alone, or when everyone has SimpleBlackBox."); return true; } switch (text) { case "bb": case "blackbox": switch ((array.Length >= 2) ? array[1].ToLowerInvariant() : "") { case "": if (!ModConfig.Enabled.Value) { ChatUtil.Send("Recorder: OFF — not recording, /bb report has nothing to analyze. Turn on with /bb on. · auto=" + (ModConfig.AutoReport.Value ? "on" : "off") + " feedback=" + (ModConfig.JumpFeedback.Value ? "on" : "off") + " · " + TestShield.StatusFragment()); return true; } ChatUtil.Send("Recorder: ON · " + $"{BlackBoxRecorder.Buffer.Count} samples buffered · " + $"{BlackBoxRecorder.IncidentsThisSession} incident(s) this session · " + "auto=" + (ModConfig.AutoReport.Value ? "on" : "off") + " feedback=" + (ModConfig.JumpFeedback.Value ? "on" : "off") + " · " + TestShield.StatusFragment()); return true; case "invuln": { string text6 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); if (text6 == "on") { TestShield.CruiserInvuln = true; ChatUtil.Send("\ud83d\udee1 Cruiser invulnerability ON (session only — resets on restart)."); } else if (text6 == "off") { TestShield.CruiserInvuln = false; ChatUtil.Send("Cruiser invulnerability OFF."); } else { ChatUtil.Send("Cruiser invulnerability is " + (TestShield.CruiserInvuln ? "ON" : "off") + ". Usage: /bb invuln on|off"); } return true; } case "god": { string text7 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); string text8 = ((array.Length >= 4) ? array[^1].ToLowerInvariant() : ""); switch (text7) { case "on": TestShield.GodMode = true; ChatUtil.Send("\ud83d\udee1 God mode ON for you (session only — resets on restart)."); return true; case "off": TestShield.GodMode = false; ChatUtil.Send("God mode OFF."); return true; case "": ChatUtil.Send("God mode is " + (TestShield.GodMode ? "ON" : "off") + ". Usage: /bb god on|off (or /bb god <name> on|off)"); return true; default: { bool flag2 = text8 == "on" || text8 == "off"; int val = array.Length - 2 - (flag2 ? 1 : 0); string name = string.Join(" ", array, 2, Math.Max(1, val)); HandleGodByName(name, flag2 ? text8 : "on"); return true; } } } case "report": if (!ModConfig.Enabled.Value && BlackBoxRecorder.Buffer.Count == 0) { ChatUtil.Send("Recorder is OFF — no telemetry to analyze. Turn on with /bb on."); return true; } BlackBoxAnalyzer.Report(null); return true; case "jumps": JumpTracker.ReportJumps(); return true; case "wheels": JumpTracker.ReportWheels(); return true; case "seed": SeedTool.Handle(array); return true; case "moon": MoonTool.Handle(array); return true; case "keyspy": KeySpy.Run(); return true; case "hp": { string text5 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); if (text5 == "on") { ModConfig.ShowHealth.Value = true; ChatUtil.Send("HP display ON."); } else if (text5 == "off") { ModConfig.ShowHealth.Value = false; ChatUtil.Send("HP display OFF."); } else { ChatUtil.Send("HP display is " + (ModConfig.ShowHealth.Value ? "ON" : "off") + ". Usage: /bb hp on|off"); } return true; } case "feedback": { string text4 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); if (text4 == "on") { ModConfig.JumpFeedback.Value = true; ChatUtil.Send("Jump feedback ON."); } else if (text4 == "off") { ModConfig.JumpFeedback.Value = false; ChatUtil.Send("Jump feedback OFF."); } else { ChatUtil.Send("Jump feedback is " + (ModConfig.JumpFeedback.Value ? "ON" : "off") + ". Usage: /bb feedback on|off"); } return true; } case "auto": { string text3 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); if (text3 == "on") { ModConfig.AutoReport.Value = true; ChatUtil.Send(ModConfig.Enabled.Value ? "Auto crash reports ON." : "Auto crash reports ON — but the recorder is OFF, so nothing will fire until /bb on."); } else if (text3 == "off") { ModConfig.AutoReport.Value = false; ChatUtil.Send("Auto crash reports OFF."); } else { ChatUtil.Send("Auto crash reports are " + (ModConfig.AutoReport.Value ? "ON" : "off") + ". Usage: /bb auto on|off"); } return true; } case "on": ModConfig.Enabled.Value = true; ChatUtil.Send("Recorder ON — buffering telemetry."); return true; case "off": ModConfig.Enabled.Value = false; ChatUtil.Send("Recorder OFF."); return true; default: ChatUtil.Send("Usage: /bb [report | jumps | seed | auto on|off | feedback on|off | hp on|off | invuln on|off | god on|off | on|off]"); return true; } case "log": case "move": case "delete": if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsHost) { ChatUtil.Send("Recall commands (/log /move /delete) are host-only."); return true; } if (!(text == "log")) { if (text == "move") { if (array.Length == 4) { RecallService.Move(array[1].ToLowerInvariant(), array[2].ToLowerInvariant(), array[3]); return true; } ChatUtil.Send("Usage: /move <me|cruiser> pos <name>"); return true; } if (array.Length == 3) { RecallService.Delete(array[1].ToLowerInvariant(), array[2]); return true; } ChatUtil.Send("Usage: /delete pos <name>"); return true; } if (array.Length == 1 || (array.Length == 2 && array[1].ToLowerInvariant() == "list")) { RecallService.List(); return true; } if (array.Length == 4) { RecallService.Log(array[1].ToLowerInvariant(), array[2].ToLowerInvariant(), array[3]); return true; } ChatUtil.Send("Usage: /log <me|cruiser> pos <name> | /log list"); return true; case "help": { string text2 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : ""); if (text2 == "") { ChatUtil.Send("SimpleBlackBox — /help blackbox"); return true; } if (text2 != "blackbox" && text2 != "bb" && text2 != "simpleblackbox" && text2 != "recall") { return false; } ChatUtil.Send("Cruiser flight recorder: telemetry at 10 Hz + every collision/damage/stress event."); ChatUtil.Send("/bb = status · /bb report = analyze now · /bb jumps = jump stats + best turbo timing · /bb feedback on|off · /bb on|off"); ChatUtil.Send("Test shields (session-only, report every hit prevented): /bb invuln on|off (cruiser) · /bb god [name] on|off (you)"); ChatUtil.Send("Seeds (host): /bb seed <number|off> forces the next landing · /bb seed scan <moon> <interior> [count] [from n]"); ChatUtil.Send("Moons: /bb moon lists them · /bb moon <name> [seed] routes there for free (no terminal cost)."); ChatUtil.Send("Recall (host): /log <me|cruiser> pos <name> · /move <me|cruiser> pos <name> · /log list · /delete pos <name>"); ChatUtil.Send("Auto post-mortem on explosion/occupant death. Files: BepInEx/config/SimpleBlackBox/ (incidents + jumps.csv)"); return true; } default: return false; } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CommandRouter.Route] {arg}"); ChatUtil.Send("Command failed — see BepInEx log."); return true; } } } internal static class ModConfig { internal static ConfigEntry<string> CommandPrefix { get; private set; } internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<float> BufferSeconds { get; private set; } internal static ConfigEntry<bool> AutoReport { get; private set; } internal static ConfigEntry<bool> JumpFeedback { get; private set; } internal static ConfigEntry<string> StoredVars { get; private set; } internal static ConfigEntry<bool> ShowHealth { get; private set; } internal static void Init(ConfigFile file) { CommandPrefix = file.Bind<string>("General", "CommandPrefix", "/", "Prefix for chat commands (e.g. /bb)."); Enabled = file.Bind<bool>("Recorder", "Enabled", false, "Record cruiser telemetry (10 Hz ring buffer + event log). OFF by default (Round 10) — the plugin stays silent until you ask for it. Also gates jump tracking and hitch detection. NOTE: nothing is buffered while off, so a crash before you enable it cannot be reported after the fact. Toggle in-game: /bb on|off."); BufferSeconds = file.Bind<float>("Recorder", "BufferSeconds", 120f, "How many seconds of telemetry the ring buffer keeps."); AutoReport = file.Bind<bool>("Recorder", "AutoReport", false, "Automatically analyze and report in chat when the cruiser is destroyed or an occupant dies. OFF by default (Round 10) — use /bb report on demand. Toggle in-game: /bb auto on|off."); JumpFeedback = file.Bind<bool>("Jumps", "Feedback", false, "Print a one-line jump report in chat after every landing (distance, airtime, turbo timing, outcome). OFF by default (Round 10). Toggle in-game: /bb feedback on|off."); StoredVars = file.Bind<string>("Recall", "StoredVars", "", "Recall variables (merged from SimpleRecall) as 'pos|name|object|planet|x,y,z|rx,ry,rz;...'. Managed in-game via /log, /move, /delete (host-only)."); ShowHealth = file.Bind<bool>("HUD", "ShowHealth", false, "Show your HP number on the HUD (above the weight counter, colored by health). Not vanilla, so OFF by default, and hidden while someone without SimpleBlackBox is in the lobby. Toggle in-game: /bb hp on|off."); } } [BepInPlugin("local.simpleblackbox", "SimpleBlackBox", "0.1.5")] public class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("local.simpleblackbox"); public static Plugin Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } private void Awake() { Instance = this; Logger = ((BaseUnityPlugin)this).Logger; ModConfig.Init(((BaseUnityPlugin)this).Config); _harmony.PatchAll(); Logger.LogInfo((object)"SimpleBlackBox v0.1.5 loaded. Cruiser flight recorder armed."); } } public static class PluginInfo { public const string Guid = "local.simpleblackbox"; public const string Name = "SimpleBlackBox"; public const string Version = "0.1.5"; } public static class MyPluginInfo { public const string PLUGIN_GUID = "SimpleBlackBox"; public const string PLUGIN_NAME = "SimpleBlackBox"; public const string PLUGIN_VERSION = "0.1.3"; } } namespace SimpleBlackBox.Patches { [HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")] internal static class ChatCommandPatch { [HarmonyPrefix] private static bool Prefix(string chatMessage, int playerId) { try { if (string.IsNullOrEmpty(chatMessage)) { return true; } if (!chatMessage.StartsWith(ModConfig.CommandPrefix.Value)) { return true; } if (!CommandRouter.IsSenderTheLocalPlayer(playerId)) { return true; } string raw = chatMessage.Substring(ModConfig.CommandPrefix.Value.Length).Trim(); bool flag = CommandRouter.Route(raw); return !flag; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ChatCommandPatch] {arg}"); return true; } } } internal static class PerfTimers { private const long ReportThresholdMs = 5L; internal static readonly Stopwatch StartMagnetingWatch = new Stopwatch(); internal static readonly Stopwatch CollectItemsWatch = new Stopwatch(); internal static readonly Stopwatch MagnetRpcWatch = new Stopwatch(); internal static void Report(string label, Stopwatch watch, string extra = null) { if (watch.ElapsedMilliseconds >= 5) { BlackBoxRecorder.AddEvent(string.Format("PERF {0} took {1}ms{2}", label, watch.ElapsedMilliseconds, (extra != null) ? (" (" + extra + ")") : "")); } } } [HarmonyPatch(typeof(VehicleController), "StartMagneting")] internal static class StartMagnetingTimerPatch { [HarmonyPrefix] private static void Prefix() { PerfTimers.StartMagnetingWatch.Restart(); } [HarmonyPostfix] private static void Postfix() { try { PerfTimers.StartMagnetingWatch.Stop(); PerfTimers.Report("StartMagneting", PerfTimers.StartMagnetingWatch); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StartMagnetingTimerPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "CollectItemsInTruck")] internal static class CollectItemsTimerPatch { private static int _itemsBefore; [HarmonyPrefix] private static void Prefix(VehicleController __instance) { try { _itemsBefore = 0; GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { if ((Object)(object)val != (Object)null && ((Component)val).transform.IsChildOf(((Component)__instance).transform)) { _itemsBefore++; } } } catch { _itemsBefore = -1; } PerfTimers.CollectItemsWatch.Restart(); } [HarmonyPostfix] private static void Postfix() { try { PerfTimers.CollectItemsWatch.Stop(); PerfTimers.Report("CollectItemsInTruck", PerfTimers.CollectItemsWatch, $"~{_itemsBefore} item(s) in truck"); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CollectItemsTimerPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "MagnetCarServerRpc")] internal static class MagnetRpcTimerPatch { [HarmonyPrefix] private static void Prefix() { PerfTimers.MagnetRpcWatch.Restart(); } [HarmonyPostfix] private static void Postfix() { try { PerfTimers.MagnetRpcWatch.Stop(); PerfTimers.Report("MagnetCarServerRpc", PerfTimers.MagnetRpcWatch); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[MagnetRpcTimerPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "DealDamageServerRpc")] internal static class ShieldDealDamagePatch { [HarmonyPrefix] private static bool Prefix(int amount) { try { if (!TestShield.CruiserInvuln || TournamentGate.Active) { return true; } TestShield.NotifyCruiserDamageSaved(amount); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShieldDealDamagePatch] {arg}"); return true; } } } [HarmonyPatch(typeof(VehicleController), "DealPermanentDamage")] internal static class ShieldPermanentDamagePatch { [HarmonyPrefix] private static bool Prefix(int damageAmount) { try { if (!TestShield.CruiserInvuln || TournamentGate.Active) { return true; } TestShield.NotifyCruiserDamageSaved(damageAmount, "permanent"); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShieldPermanentDamagePatch] {arg}"); return true; } } } [HarmonyPatch(typeof(VehicleController), "SetInternalStress")] internal static class ShieldStressPatch { [HarmonyPrefix] private static bool Prefix() { if (TestShield.CruiserInvuln) { return TournamentGate.Active; } return true; } } [HarmonyPatch(typeof(VehicleController), "DestroyCarServerRpc")] internal static class ShieldDestroyRpcPatch { [HarmonyPrefix] private static bool Prefix() { try { if (!TestShield.CruiserInvuln || TournamentGate.Active) { return true; } TestShield.NotifyCruiserDestructionSaved(); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShieldDestroyRpcPatch] {arg}"); return true; } } } [HarmonyPatch(typeof(VehicleController), "DestroyCar")] internal static class ShieldDestroyPatch { [HarmonyPrefix] private static bool Prefix() { try { if (!TestShield.CruiserInvuln || TournamentGate.Active) { return true; } TestShield.NotifyCruiserDestructionSaved("local path"); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShieldDestroyPatch] {arg}"); return true; } } } [HarmonyPatch(typeof(PlayerControllerB), "DamagePlayer")] internal static class ShieldPlayerDamagePatch { [HarmonyPrefix] private static bool Prefix(PlayerControllerB __instance, int damageNumber, CauseOfDeath causeOfDeath, bool fallDamage) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) try { if (!TestShield.GodMode || TournamentGate.Active) { return true; } if ((Object)(object)__instance != (Object)(object)GameNetworkManager.Instance?.localPlayerController) { return true; } TestShield.NotifyPlayerDamageSaved(damageNumber, string.Format("{0}{1}", causeOfDeath, fallDamage ? ", fall" : "")); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShieldPlayerDamagePatch] {arg}"); return true; } } } [HarmonyPatch(typeof(PlayerControllerB), "KillPlayer")] internal static class ShieldPlayerKillPatch { [HarmonyPrefix] private unsafe static bool Prefix(PlayerControllerB __instance, CauseOfDeath causeOfDeath) { try { if (!TestShield.GodMode || TournamentGate.Active) { return true; } if ((Object)(object)__instance != (Object)(object)GameNetworkManager.Instance?.localPlayerController) { return true; } TestShield.NotifyPlayerDeathSaved(((object)(*(CauseOfDeath*)(&causeOfDeath))/*cast due to .constrained prefix*/).ToString()); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShieldPlayerKillPatch] {arg}"); return true; } } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal static class TerminalCommandPatch { private static TerminalNode _node; private static readonly HashSet<string> Subcommands = new HashSet<string> { "seed", "report", "jumps", "wheels", "keyspy", "hp", "feedback", "auto", "on", "off", "god", "invuln", "log", "move", "delete", "help" }; [HarmonyPrefix] private static bool Prefix(Terminal __instance, ref TerminalNode __result) { try { string text = __instance.screenText.text; int num = Mathf.Clamp(__instance.textAdded, 0, text.Length); string text2 = __instance.RemovePunctuation(text.Substring(text.Length - num)).Trim().ToLowerInvariant(); if (text2.Length == 0) { return true; } bool flag = (Object)(object)_node != (Object)null && (Object)(object)__instance.currentNode == (Object)(object)_node; string[] array = text2.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string text3; if (array[0] == "bb" || array[0] == "blackbox") { text3 = string.Join(" ", array, 1, array.Length - 1); } else { if (!flag || !Subcommands.Contains(array[0])) { return true; } text3 = text2; } if (TournamentGate.Active) { return true; } string text4 = ((!TestingGate.Unlocked) ? "SimpleBlackBox is a testing tool: its commands are off while someone without SimpleBlackBox is in the lobby. They work again when you're alone, or when everyone has SimpleBlackBox." : ((text3.Length != 0 && !(text3 == "help")) ? Run(text3) : Menu())); if ((Object)(object)_node == (Object)null) { _node = ScriptableObject.CreateInstance<TerminalNode>(); _node.clearPreviousText = true; } _node.maxCharactersToType = 60; _node.displayText = "\n\n\nBLACKBOX\n\n" + text4 + "\n\n"; __result = _node; return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TerminalCommandPatch] {arg}"); ChatUtil.Capture = null; return true; } } private static string Run(string sub) { string text; switch (sub.Split(' ')[0]) { default: text = "bb " + sub; break; case "log": case "move": case "delete": text = sub; break; } string text2 = text; List<string> list = (ChatUtil.Capture = new List<string>()); try { CommandRouter.Route(text2); } finally { ChatUtil.Capture = null; } if (list.Count == 0) { return "Ran: " + text2 + "\n(Any reply shows in chat.)"; } return string.Join("\n", list).Replace(" · ", "\n"); } private static string Menu() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Type these on this screen (no \"bb\" needed):\n\n"); stringBuilder.Append("seed current / forced seed\n"); stringBuilder.Append("seed <number> force the next landings\n"); stringBuilder.Append("seed off random seeds again\n"); stringBuilder.Append("seed scan <moon> <interior> [count] [from n]\n"); stringBuilder.Append(" e.g. seed scan experimentation mansion 20\n\n"); stringBuilder.Append("moon list the moons\n"); stringBuilder.Append("moon <name> [seed] route there, free\n\n"); stringBuilder.Append("report jumps wheels keyspy\n"); stringBuilder.Append("on|off auto on|off feedback on|off hp on|off\n"); stringBuilder.Append("god on|off invuln on|off\n"); stringBuilder.Append("log list log me pos <name> move me pos <name>"); return stringBuilder.ToString(); } } [HarmonyPatch(typeof(VehicleController), "CarCollisionServerRpc")] internal static class CarCollisionPatch { [HarmonyPostfix] private static void Postfix(Vector3 vel, float magn) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { BlackBoxRecorder.AddEvent($"COLLISION magn={magn:0.#} vel=({vel.x:0.#},{vel.y:0.#},{vel.z:0.#}) |v|={((Vector3)(ref vel)).magnitude:0.#}"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CarCollisionPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "CarBumpServerRpc")] internal static class CarBumpPatch { [HarmonyPostfix] private static void Postfix(Vector3 vel) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { BlackBoxRecorder.AddEvent($"BUMP vel=({vel.x:0.#},{vel.y:0.#},{vel.z:0.#})"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CarBumpPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "DealDamageServerRpc")] internal static class DealDamagePatch { [HarmonyPostfix] private static void Postfix(int amount, int sentByClient) { try { if (!TestShield.CruiserInvuln && !((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { BlackBoxRecorder.AddEvent($"DAMAGE -{amount} HP (reported by client {sentByClient})"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DealDamagePatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "DealPermanentDamage")] internal static class DealPermanentDamagePatch { [HarmonyPostfix] private static void Postfix(int damageAmount, Vector3 damagePosition) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) try { if (!TestShield.CruiserInvuln) { BlackBoxRecorder.AddEvent($"PERMANENT DAMAGE -{damageAmount} at ({damagePosition.x:0.#},{damagePosition.y:0.#},{damagePosition.z:0.#})"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DealPermanentDamagePatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "SetInternalStress")] internal static class SetInternalStressPatch { [HarmonyPostfix] private static void Postfix(VehicleController __instance, float carStressIncrease) { try { if (!TestShield.CruiserInvuln && carStressIncrease > 0.01f) { BlackBoxRecorder.AddEvent($"STRESS +{carStressIncrease:0.##} (type={__instance.lastStressType}, total={__instance.carStress:0.##})"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SetInternalStressPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "DamagePlayerInVehicle")] internal static class DamagePlayerInVehiclePatch { [HarmonyPostfix] private static void Postfix(Vector3 vel, float magnitude) { try { BlackBoxRecorder.AddEvent($"OCCUPANT HURT by impact magn={magnitude:0.#} |v|={((Vector3)(ref vel)).magnitude:0.#}"); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DamagePlayerInVehiclePatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "DestroyCar")] internal static class DestroyCarPatch { [HarmonyPostfix] private static void Postfix() { try { if (!TestShield.CruiserInvuln) { BlackBoxRecorder.AddEvent("DESTROYED — the cruiser is gone"); BlackBoxAnalyzer.OnIncident("the cruiser was destroyed"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DestroyCarPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "ShiftToGearServerRpc")] internal static class GearShiftPatch { [HarmonyPostfix] private static void Postfix(int setGear, int playerId) { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { BlackBoxRecorder.AddEvent($"GEAR → {(object)(CarGearShift)setGear}"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[GearShiftPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "PushTruckServerRpc")] internal static class PushTruckPatch { [HarmonyPostfix] private static void Postfix(Vector3 pos, Vector3 dir) { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { BlackBoxRecorder.AddEvent("PUSHED by hand"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[PushTruckPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "MagnetCarServerRpc")] internal static class MagnetCarPatch { [HarmonyPostfix] private static void Postfix() { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { BlackBoxRecorder.AddEvent("MAGNETTED to the ship"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[MagnetCarPatch] {arg}"); } } } [HarmonyPatch(typeof(VehicleController), "UseTurboBoostServerRpc")] internal static class TurboBoostPatch { [HarmonyPostfix] private static void Postfix() { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer) { JumpTracker.OnTurbo(); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TurboBoostPatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class StartupPatch { [HarmonyPostfix] private static void Postfix() { try { TestingGate.Reset(); TestingGate.Register(); TournamentGate.Register(); BlackBoxRecorder.EnsureRunning(); JumpTracker.EnsureRunning(); HitchDetector.EnsureRunning(); ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(Line()); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StartupPatch] {arg}"); } } private static IEnumerator Line() { yield return (object)new WaitForSeconds(3f); SuiteAnnounce.MaybeAnnounce("local.simpleblackbox"); } } } namespace SimpleBlackBox.Features { internal static class BlackBoxAnalyzer { private static float _lastIncidentT; internal static void OnIncident(string trigger) { try { if (ModConfig.Enabled.Value && ModConfig.AutoReport.Value && !(Time.realtimeSinceStartup - _lastIncidentT < 8f)) { _lastIncidentT = Time.realtimeSinceStartup; BlackBoxRecorder.IncidentsThisSession++; Report(trigger); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[BlackBoxAnalyzer.OnIncident] {arg}"); } } internal static void Report(string trigger) { List<string> list = Analyze(trigger); string analysis = string.Join("\r\n", list); string text = BlackBoxRecorder.WriteIncidentFile(analysis); foreach (string item in list.Take(6)) { ChatUtil.Send(item); } if (text != null) { ChatUtil.Send("Full data: " + Path.GetFileName(text) + " (SimpleBlackBox folder in config)."); } } private static List<string> Analyze(string trigger) { List<string> list = new List<string>(); IReadOnlyList<Sample> buffer = BlackBoxRecorder.Buffer; IReadOnlyList<BoxEvent> events = BlackBoxRecorder.Events; float now = Time.realtimeSinceStartup; list.Add("⬛ Incident: " + (trigger ?? "manual report")); if (buffer.Count == 0) { list.Add("No telemetry recorded (was the cruiser present and the recorder on?)."); return list; } List<Sample> window = buffer.Where((Sample s) => now - s.T <= 15f).ToList(); if (window.Count == 0) { window = buffer.ToList(); } List<BoxEvent> list2 = events.Where((BoxEvent e) => now - e.T <= 15f).ToList(); Sample sample = window[window.Count - 1]; float topSpeed = window.Max((Sample s) => s.Speed); float num = window.Min((Sample s) => s.VelY); float num2 = (float)window.Count((Sample s) => s.UpDot < 0.2f) * 0.1f; bool flag = window.Any((Sample s) => s.ExtremeStress); int hp = window[0].Hp; int hp2 = sample.Hp; bool flag2 = list2.Any((BoxEvent e) => e.Text.StartsWith("DESTROYED")); if (flag2 && flag) { float t = window.FirstOrDefault((Sample s) => s.ExtremeStress).T; float num3 = now - t; List<string> list3 = (from s in window select s.StressType into value where !string.IsNullOrEmpty(value) select value).Distinct().ToList(); list.Add($"Cause: EXPLODED FROM STRESS — under extreme stress for {num3:0.#}s before detonating."); if (list3.Count > 0) { list.Add("Stress source(s) the game recorded: " + string.Join(", ", list3) + "."); } } else if (flag2 && hp2 <= 0 && hp > 0) { List<BoxEvent> list4 = list2.Where((BoxEvent e) => e.Text.StartsWith("DAMAGE") || e.Text.StartsWith("HP ") || e.Text.StartsWith("COLLISION")).ToList(); list.Add($"Cause: BATTERED TO DEATH — HP {hp}→0 over the last {now - window[0].T:0.#}s across {list4.Count} recorded hit(s)."); } else if (flag2) { list.Add("Cause: destroyed — see the event timeline (no stress flag or HP staircase captured in the window)."); } if (trigger != null && trigger.Contains("Inertia")) { Sample sample2 = window.FirstOrDefault((Sample s) => s.Speed == topSpeed); list.Add($"Inertia = sudden stop: the truck went {topSpeed:0.#} m/s and stopped almost instantly — the driver didn't."); BoxEvent boxEvent = list2.LastOrDefault((BoxEvent e) => e.Text.StartsWith("HARD STOP")); if (boxEvent.Text != null) { list.Add("Impact record: " + boxEvent.Text); } } else if (trigger != null && trigger.Contains("Gravity")) { list.Add($"Gravity = the fall did it: peak descent {num:0.#} m/s in the window."); BoxEvent boxEvent2 = list2.LastOrDefault((BoxEvent e) => e.Text.StartsWith("LANDED")); if (boxEvent2.Text != null) { list.Add("Landing record: " + boxEvent2.Text); } } else if (trigger != null && trigger.Contains("Crushing")) { list.Add((num2 > 0.3f) ? $"Crushing with {num2:0.#}s of rollover in the window — the truck likely rolled onto them." : "Crushing without recorded rollover — pinned against terrain/object, or the magnet."); } else if (trigger != null && trigger.Contains("Blast")) { list.Add(flag2 ? "Blast: killed by the truck's own explosion." : "Blast: an external explosion (mine/lightning?) — the truck itself survived."); } else if (trigger != null && trigger.Contains("Drowning")) { list.Add("Drowning — the truck went into water."); } List<string> list5 = new List<string>(); if (topSpeed > 20f) { list5.Add($"top speed {topSpeed:0.#} m/s"); } if (window.Any((Sample s) => s.Turbo < window[0].Turbo) || window.Any((Sample s) => s.Turbo > 0)) { list5.Add($"turbo aboard ({window[0].Turbo} at window start)"); } if (num < -12f) { list5.Add($"airborne (descent {num:0.#} m/s)"); } if (num2 > 0.3f) { list5.Add($"rolled over for {num2:0.#}s"); } if (sample.Brake) { list5.Add("brake was pressed at the end"); } if (sample.Magnetted) { list5.Add("magnetted to the ship"); } if (list5.Count > 0) { list.Add("Factors: " + string.Join(" · ", list5) + "."); } if (list2.Count > 0) { IEnumerable<string> values = from e in list2.Skip(Math.Max(0, list2.Count - 5)) select $"t-{now - e.T:0.0}s {e.Text}"; list.Add("Trail: " + string.Join(" | ", values)); } return list; } } internal struct Sample { internal float T; internal Vector3 Pos; internal float UpDot; internal float Speed; internal float VelY; internal float Steering; internal bool Brake; internal string Gear; internal float Rpm; internal int Hp; internal float Stress; internal string StressType; internal bool ExtremeStress; internal bool Ignition; internal bool Magnetted; internal int Turbo; internal string Driver; internal int DriverHealth; internal string Line() { return string.Format(CultureInfo.InvariantCulture, "{0:0.0}s pos=({1:0.#},{2:0.#},{3:0.#}) spd={4:0.#} velY={5:0.#} up={6:0.##} steer={7:0.##} brake={8} gear={9} rpm={10:0} hp={11} stress={12:0.##}{13}{14} ign={15} mag={16} turbo={17} driver={18}({19}hp)", T, Pos.x, Pos.y, Pos.z, Speed, VelY, UpDot, Steering, Brake ? 1 : 0, Gear, Rpm, Hp, Stress, string.IsNullOrEmpty(StressType) ? "" : ("(" + StressType + ")"), ExtremeStress ? " EXTREME" : "", Ignition ? 1 : 0, Magnetted ? 1 : 0, Turbo, Driver ?? "-", (Driver != null) ? DriverHealth : 0); } } internal struct BoxEvent { internal float T; internal string Text; } internal static class BlackBoxRecorder { private const float SampleInterval = 0.1f; private const float RecentOccupantWindow = 5f; private static readonly List<Sample> _buffer = new List<Sample>(); private static readonly List<BoxEvent> _events = new List<BoxEvent>(); private static bool _running; private static Vector3 _lastPos; private static float _lastPosT; private static Sample _prev; private static bool _hasPrev; private static PlayerControllerB _lastDriver; private static PlayerControllerB _lastPassenger; private static float _lastOccupantExitT; private static PlayerControllerB _recentOccupant; private static bool _prevDriverDead; private static bool _prevPassengerDead; private static float _lastTeleportEventT; internal static int IncidentsThisSession; internal static IReadOnlyList<Sample> Buffer => _buffer; internal static IReadOnlyList<BoxEvent> Events => _events; internal static string OutputDir { get { string text = Path.Combine(Paths.ConfigPath, "SimpleBlackBox"); Directory.CreateDirectory(text); return text; } } internal static void EnsureRunning() { if (!_running) { _running = true; ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(SampleLoop()); } } internal static void AddEvent(string text) { try { _events.Add(new BoxEvent { T = Time.realtimeSinceStartup, Text = text }); TrimTo(BufferWindow()); ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)("[BlackBox] " + text)); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[BlackBoxRecorder.AddEvent] {arg}"); } } private static float BufferWindow() { return Mathf.Max(30f, ModConfig.BufferSeconds.Value); } private static void TrimTo(float window) { float cutoff = Time.realtimeSinceStartup - window; _buffer.RemoveAll((Sample s) => s.T < cutoff); _events.RemoveAll((BoxEvent e) => e.T < cutoff); } private static IEnumerator SampleLoop() { WaitForSeconds wait = new WaitForSeconds(0.1f); while (true) { yield return wait; try { if (ModConfig.Enabled.Value) { VehicleController val = Object.FindObjectOfType<VehicleController>(); if ((Object)(object)val == (Object)null) { _hasPrev = false; _lastDriver = null; _lastPassenger = null; } else { Take(val); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[BlackBoxRecorder.SampleLoop] {arg}"); } } } private static void Take(VehicleController v) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) float realtimeSinceStartup = Time.realtimeSinceStartup; Vector3 position = ((Component)v).transform.position; Vector3 val = (((Object)(object)v.mainRigidbody != (Object)null && !v.mainRigidbody.isKinematic) ? v.mainRigidbody.velocity : ((!(_lastPosT > 0f) || !(realtimeSinceStartup > _lastPosT)) ? Vector3.zero : ((position - _lastPos) / (realtimeSinceStartup - _lastPosT)))); _lastPos = position; _lastPosT = realtimeSinceStartup; Sample sample = new Sample { T = realtimeSinceStartup, Pos = position, UpDot = Vector3.Dot(((Component)v).transform.up, Vector3.up), Speed = ((Vector3)(ref val)).magnitude, VelY = val.y, Steering = v.steeringInput, Brake = v.brakePedalPressed, Gear = ((object)Unsafe.As<CarGearShift, CarGearShift>(ref v.gear)/*cast due to .constrained prefix*/).ToString(), Rpm = v.EngineRPM, Hp = v.carHP, Stress = v.carStress, StressType = v.lastStressType, ExtremeStress = (v.underExtremeStress || v.syncedExtremeStress), Ignition = v.ignitionStarted, Magnetted = v.magnetedToShip, Turbo = v.turboBoosts, Driver = (((Object)(object)v.currentDriver != (Object)null) ? v.currentDriver.playerUsername : null), DriverHealth = (((Object)(object)v.currentDriver != (Object)null) ? v.currentDriver.health : 0) }; DetectDerivedEvents(v, sample); _buffer.Add(sample); TrimTo(BufferWindow()); _prev = sample; _hasPrev = true; } private static void DetectDerivedEvents(VehicleController v, Sample s) { //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)v.currentDriver != (Object)(object)_lastDriver) { if ((Object)(object)v.currentDriver != (Object)null) { AddEvent("DRIVER IN: " + v.currentDriver.playerUsername); } else if ((Object)(object)_lastDriver != (Object)null) { AddEvent("DRIVER OUT: " + _lastDriver.playerUsername); _recentOccupant = _lastDriver; _lastOccupantExitT = s.T; } _prevDriverDead = (Object)(object)v.currentDriver != (Object)null && v.currentDriver.isPlayerDead; _lastDriver = v.currentDriver; } if ((Object)(object)v.currentPassenger != (Object)(object)_lastPassenger) { if ((Object)(object)v.currentPassenger != (Object)null) { AddEvent("PASSENGER IN: " + v.currentPassenger.playerUsername); } else if ((Object)(object)_lastPassenger != (Object)null) { AddEvent("PASSENGER OUT: " + _lastPassenger.playerUsername); _recentOccupant = _lastPassenger; _lastOccupantExitT = s.T; } _prevPassengerDead = (Object)(object)v.currentPassenger != (Object)null && v.currentPassenger.isPlayerDead; _lastPassenger = v.currentPassenger; } CheckDeath(v.currentDriver, ref _prevDriverDead, "DRIVER"); CheckDeath(v.currentPassenger, ref _prevPassengerDead, "PASSENGER"); if ((Object)(object)_recentOccupant != (Object)null && s.T - _lastOccupantExitT <= 5f && _recentOccupant.isPlayerDead) { AddEvent($"DEATH (just exited): {_recentOccupant.playerUsername} cause={_recentOccupant.causeOfDeath}"); BlackBoxAnalyzer.OnIncident($"{_recentOccupant.playerUsername} died ({_recentOccupant.causeOfDeath}) within {5f:0}s of leaving the cruiser"); _recentOccupant = null; } if (!_hasPrev) { return; } if (s.Speed > 80f || _prev.Speed > 80f) { if (s.T - _lastTeleportEventT > 1f) { _lastTeleportEventT = s.T; AddEvent($"TELEPORT detected ({Vector3.Distance(_prev.Pos, s.Pos):0.#}m in one sample — SimpleRecall /move or similar)"); } return; } float num = Mathf.Max(0.0001f, s.T - _prev.T); if (s.Hp < _prev.Hp) { AddEvent($"HP {_prev.Hp}→{s.Hp}"); } if (s.UpDot < 0.2f && _prev.UpDot >= 0.2f) { AddEvent($"ROLLOVER (up={s.UpDot:0.##}) at {s.Speed:0.#} m/s"); } else if (s.UpDot >= 0.2f && _prev.UpDot < 0.2f) { AddEvent("UPRIGHT again"); } float num2 = (_prev.Speed - s.Speed) / num; if (_prev.Speed > 8f && num2 > 60f) { AddEvent($"HARD STOP: {_prev.Speed:0.#}→{s.Speed:0.#} m/s in {num * 1000f:0}ms (decel {num2:0} m/s²)"); } if (_prev.VelY < -12f && s.VelY > -3f) { AddEvent($"LANDED after fall (peak descent {_prev.VelY:0.#} m/s)"); } if (s.ExtremeStress && !_prev.ExtremeStress) { AddEvent($"EXTREME STRESS began (stress={s.Stress:0.##}, type={s.StressType})"); } else if (!s.ExtremeStress && _prev.ExtremeStress) { AddEvent("extreme stress eased"); } } private static void CheckDeath(PlayerControllerB occupant, ref bool prevDead, string label) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)occupant == (Object)null)) { bool isPlayerDead = occupant.isPlayerDead; if (isPlayerDead && !prevDead) { AddEvent($"DEATH ({label}): {occupant.playerUsername} cause={occupant.causeOfDeath}"); BlackBoxAnalyzer.OnIncident($"{occupant.playerUsername} died in the {label.ToLowerInvariant()} seat ({occupant.causeOfDeath})"); } prevDead = isPlayerDead; } } internal static string WriteIncidentFile(string analysis) { try { string text = Path.Combine(OutputDir, $"incident_{DateTime.Now:yyyy-MM-dd_HHmmss}.txt"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("==== SimpleBlackBox incident report ===="); stringBuilder.AppendLine($"written {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); stringBuilder.AppendLine(); stringBuilder.AppendLine("---- ANALYSIS ----"); stringBuilder.AppendLine(analysis); stringBuilder.AppendLine(); stringBuilder.AppendLine("---- EVENTS ----"); float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (BoxEvent @event in _events) { stringBuilder.AppendLine($"t-{realtimeSinceStartup - @event.T,6:0.0}s {@event.Text}"); } stringBuilder.AppendLine(); stringBuilder.AppendLine($"---- TELEMETRY ({_buffer.Count} samples @ {0.1f:0.0}s) ----"); foreach (Sample item in _buffer) { stringBuilder.AppendLine($"t-{realtimeSinceStartup - item.T,6:0.0}s {item.Line()}"); } File.WriteAllText(text, stringBuilder.ToString()); return text; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[BlackBoxRecorder.WriteIncidentFile] {arg}"); return null; } } } internal static class HitchDetector { private const float AbsoluteThreshold = 0.08f; private const float RelativeFactor = 3f; private const float ContextRefreshInterval = 2f; private static bool _running; private static float _avgDt = 1f / 60f; private static float _lastHitchLogT; private static float _nextContextRefresh; private static int _cruiserItems = -1; private static VehicleController _vehicle; internal static void EnsureRunning() { if (!_running) { _running = true; ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(FrameLoop()); } } private static IEnumerator FrameLoop() { while (true) { yield return null; try { Tick(); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[HitchDetector] {arg}"); } } } private static void Tick() { //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) float unscaledDeltaTime = Time.unscaledDeltaTime; float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= _nextContextRefresh) { _nextContextRefresh = realtimeSinceStartup + 2f; RefreshContext(); } bool flag = unscaledDeltaTime > 0.08f && unscaledDeltaTime > _avgDt * 3f; if (!flag) { _avgDt = Mathf.Lerp(_avgDt, unscaledDeltaTime, 0.05f); } if (flag && ModConfig.Enabled.Value && !(realtimeSinceStartup - _lastHitchLogT < 0.5f)) { _lastHitchLogT = realtimeSinceStartup; string text = "no cruiser"; if ((Object)(object)_vehicle != (Object)null) { Transform val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.magnetPoint : null); float num = (((Object)(object)val != (Object)null) ? Vector3.Distance(((Component)_vehicle).transform.position, val.position) : (-1f)); text = string.Format("magnetDist {0}, magneted={1}, cruiser items~{2}", (num >= 0f) ? $"{num:0.#}m" : "?", _vehicle.magnetedToShip, _cruiserItems); } int num2 = ((!((Object)(object)StartOfRound.Instance != (Object)null)) ? 1 : (StartOfRound.Instance.connectedPlayersAmount + 1)); BlackBoxRecorder.AddEvent($"HITCH {unscaledDeltaTime * 1000f:0}ms (avg {_avgDt * 1000f:0.0}ms) — {text}, players={num2}"); } } private static void RefreshContext() { _vehicle = Object.FindObjectOfType<VehicleController>(); if ((Object)(object)_vehicle == (Object)null) { _cruiserItems = -1; return; } int num = 0; GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { if ((Object)(object)val != (Object)null && ((Component)val).transform.IsChildOf(((Component)_vehicle).transform)) { num++; } } _cruiserItems = num; } } [HarmonyPatch(typeof(HUDManager), "Update")] internal static class HpHud { private static TextMeshProUGUI _text; private static Color _baseColor; private static int _lastHp = int.MinValue; private static bool _shown; private static readonly Color MidColor = new Color(1f, 0.82f, 0.29f); private static readonly Color LowColor = new Color(1f, 0.25f, 0.25f); [HarmonyPostfix] private static void Postfix(HUDManager __instance) { //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) try { if (!ModConfig.ShowHealth.Value || !TestingGate.Unlocked || TournamentGate.Active) { if ((Object)(object)_text != (Object)null && _shown) { ((Component)_text).gameObject.SetActive(false); _shown = false; } return; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return; } if ((Object)(object)_text == (Object)null) { TextMeshProUGUI weightCounter = __instance.weightCounter; if ((Object)(object)weightCounter == (Object)null) { return; } GameObject val2 = Object.Instantiate<GameObject>(((Component)weightCounter).gameObject, ((TMP_Text)weightCounter).transform.parent); ((Object)val2).name = "SimpleBB_HpCounter"; Animator[] componentsInChildren = val2.GetComponentsInChildren<Animator>(); foreach (Animator val3 in componentsInChildren) { Object.Destroy((Object)(object)val3); } _text = val2.GetComponent<TextMeshProUGUI>(); ((TMP_Text)_text).transform.localPosition = ((TMP_Text)weightCounter).transform.localPosition + new Vector3(10f, 26f, 0f); _baseColor = ((Graphic)weightCounter).color; _lastHp = int.MinValue; } bool flag = !val.isPlayerDead; if (flag != _shown) { ((Component)_text).gameObject.SetActive(flag); _shown = flag; } if (flag) { int health = val.health; if (health != _lastHp) { _lastHp = health; ((TMP_Text)_text).text = $"{health} hp"; ((Graphic)_text).color = ((health > 50) ? Color.Lerp(MidColor, _baseColor, (float)(health - 50) / 50f) : Color.Lerp(LowColor, MidColor, (float)health / 50f)); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[HpHud] {arg}"); } } } internal static class JumpTracker { private const float MinLaunchSpeed = 4f; private const float MinAirtime = 0.25f; private const float VehicleRefreshInterval = 2f; private static bool _running; private static VehicleController _vehicle; private static float _nextVehicleRefresh; private static readonly List<float> _turboTimes = new List<float>(); private static int _sessionJumps; private static bool _airborne; private static float _launchT; private static Vector3 _launchPos; private static float _launchSpeed; private static float _launchVelY; private static string _launchGear; private static int _launchHp; private static string _driver; private static float _maxY; private static Vector3 _lastPos; private static float _lastPosT; internal static string CsvPath => Path.Combine(BlackBoxRecorder.OutputDir, "jumps.csv"); internal static void EnsureRunning() { if (!_running) { _running = true; ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(FrameLoop()); } } internal static void OnTurbo() { _turboTimes.Add(Time.realtimeSinceStartup); _turboTimes.RemoveAll((float t) => Time.realtimeSinceStartup - t > 30f); BlackBoxRecorder.AddEvent("TURBO pressed"); } private static IEnumerator FrameLoop() { while (true) { yield return null; try { Tick(); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JumpTracker] {arg}"); } } } private static void Tick() { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.Enabled.Value) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if ((Object)(object)_vehicle == (Object)null || realtimeSinceStartup >= _nextVehicleRefresh) { _vehicle = Object.FindObjectOfType<VehicleController>(); _nextVehicleRefresh = realtimeSinceStartup + 2f; } VehicleController vehicle = _vehicle; if ((Object)(object)vehicle == (Object)null || vehicle.carDestroyed) { if (_airborne && (Object)(object)vehicle != (Object)null && vehicle.carDestroyed) { FinishJump(vehicle, realtimeSinceStartup, destroyed: true); } _airborne = false; return; } Vector3 position = ((Component)vehicle).transform.position; Vector3 val = (((Object)(object)vehicle.mainRigidbody != (Object)null && !vehicle.mainRigidbody.isKinematic) ? vehicle.mainRigidbody.velocity : ((!(_lastPosT > 0f) || !(realtimeSinceStartup > _lastPosT)) ? Vector3.zero : ((position - _lastPos) / (realtimeSinceStartup - _lastPosT)))); _lastPos = position; _lastPosT = realtimeSinceStartup; if (((Vector3)(ref val)).magnitude > 80f) { _airborne = false; return; } int num = CountGroundedWheels(vehicle); float num2 = GroundRayDistance(vehicle); if (!_airborne) { if (num2 > 2.5f && !vehicle.magnetedToShip && (Object)(object)vehicle.currentDriver != (Object)null && ((Vector3)(ref val)).magnitude > 4f) { _airborne = true; _launchT = realtimeSinceStartup; _launchPos = position; _launchSpeed = ((Vector3)(ref val)).magnitude; _launchVelY = val.y; _launchGear = ((object)Unsafe.As<CarGearShift, CarGearShift>(ref vehicle.gear)/*cast due to .constrained prefix*/).ToString(); _launchHp = vehicle.carHP; _driver = vehicle.currentDriver.playerUsername; _maxY = position.y; } } else { if (position.y > _maxY) { _maxY = position.y; } if (vehicle.magnetedToShip) { _airborne = false; } else if (num >= 2 || num2 <= 1.2f) { FinishJump(vehicle, realtimeSinceStartup, destroyed: false, val); } } } private static float GroundRayDistance(VehicleController v) { string hitName; return GroundRayDistance(v, out hitName); } private static float GroundRayDistance(VehicleController v, out string hitName) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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_0054: Unknown result type (might be due to invalid IL or missing references) hitName = "(nothing)"; int num = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.collidersAndRoomMaskAndDefault : (-1)); RaycastHit[] array = Physics.RaycastAll(((Component)v).transform.position, Vector3.down, 30f, num, (QueryTriggerInteraction)1); float num2 = float.MaxValue; RaycastHit[] array2 = array; for (int i = 0; i < array2.Length; i++) { RaycastHit val = array2[i]; if (!((Object)(object)((RaycastHit)(ref val)).collider == (Object)null) && !((Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<VehicleController>() == (Object)(object)v) && ((RaycastHit)(ref val)).distance < num2) { num2 = ((RaycastHit)(ref val)).distance; hitName = ((Object)((RaycastHit)(ref val)).collider).name; } } return num2; } internal static void ReportWheels() { VehicleController val = Object.FindObjectOfType<VehicleController>(); if ((Object)(object)val == (Object)null) { ChatUtil.Send("No cruiser found."); return; } string arg = $"FL={(((Object)(object)val.FrontLeftWheel != (Object)null && val.FrontLeftWheel.isGrounded) ? 1 : 0)} " + $"FR={(((Object)(object)val.FrontRightWheel != (Object)null && val.FrontRightWheel.isGrounded) ? 1 : 0)} " + $"BL={(((Object)(object)val.BackLeftWheel != (Object)null && val.BackLeftWheel.isGrounded) ? 1 : 0)} " + $"BR={(((Object)(object)val.BackRightWheel != (Object)null && val.BackRightWheel.isGrounded) ? 1 : 0)}"; int num = ((val.otherWheels != null) ? val.otherWheels.Length : 0); int num2 = 0; if (val.otherWheels != null) { WheelCollider[] otherWheels = val.otherWheels; foreach (WheelCollider val2 in otherWheels) { if ((Object)(object)val2 != (Object)null && val2.isGrounded) { num2++; } } } string hitName; float num3 = GroundRayDistance(val, out hitName); string text = $"Wheels: {arg} · other {num2}/{num} grounded · " + string.Format("ray-to-ground {0} (hit: {1}) · airborne={2}", (num3 == float.MaxValue) ? ">30m" : $"{num3:0.00}m", hitName, _airborne); ChatUtil.Send(text); ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)("[BlackBox] /bb wheels → " + text)); } } private static void FinishJump(VehicleController v, float now, bool destroyed, Vector3 landVel = default(Vector3)) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) _airborne = false; float num = now - _launchT; if (num < 0.25f && !destroyed) { return; } Vector3 position = ((Component)v).transform.position; Vector3 val = position - _launchPos; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; float heightGain = _maxY - _launchPos.y; float num2 = Vector3.Dot(((Component)v).transform.up, Vector3.up); bool flag = num2 >= 0.5f; int num3 = Mathf.Max(0, _launchHp - v.carHP); string text = ((_launchVelY >= -1f) ? "JUMP" : "DROP"); float? turboOffset = null; List<float> list = _turboTimes.Where((float t) => t >= _launchT - 1f && t <= now).ToList(); if (list.Count > 0) { turboOffset = list.OrderBy((float t) => Mathf.Abs(t - _launchT)).First() - _launchT; } _sessionJumps++; string moon = (((Object)(object)StartOfRound.Instance?.currentLevel != (Object)null) ? StartOfRound.Instance.currentLevel.PlanetName : "?"); AppendCsv(moon, magnitude, num, heightGain, turboOffset, landVel, num2, flag, num3, destroyed, text); string text2 = (turboOffset.HasValue ? string.Format(" · turbo {0}{1:0.00}s", (turboOffset.Value >= 0f) ? "+" : "", turboOffset.Value) : " · no turbo"); string text3 = (destroyed ? "DESTROYED on landing" : (flag ? "landed upright" : $"landed ROLLED (up={num2:0.##})")); string text4 = ((num3 > 0) ? $" · -{num3} HP" : ""); BlackBoxRecorder.AddEvent($"{text}: {magnitude:0.#}m, air {num:0.00}s{text2}, {text3}{text4}"); if (ModConfig.JumpFeedback.Value && text == "JUMP") { ChatUtil.Send($"JUMP #{_sessionJumps}: {magnitude:0.#}m · air {num:0.00}s · launch {_launchSpeed:0.#} m/s{text2} · {text3}{text4}"); } } private static int CountGroundedWheels(VehicleController v) { int num = 0; if ((Object)(object)v.FrontLeftWheel != (Object)null && v.FrontLeftWheel.isGrounded) { num++; } if ((Object)(object)v.FrontRightWheel != (Object)null && v.FrontRightWheel.isGrounded) { num++; } if ((Object)(object)v.BackLeftWheel != (Object)null && v.BackLeftWheel.isGrounded) { num++; } if ((Object)(object)v.BackRightWheel != (Object)null && v.BackRightWheel.isGrounded) { num++; } return num; } private static void AppendCsv(string moon, float distance, float airtime, float heightGain, float? turboOffset, Vector3 landVel, float upDot, bool upright, int hpLost, bool destroyed, string type) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) try { bool flag = !File.Exists(CsvPath); using StreamWriter streamWriter = File.AppendText(CsvPath); if (flag) { streamWriter.WriteLine("datetime,moon,type,driver,launchX,launchY,launchZ,launchSpeed,launchVelY,gear,turboOffsetS,airtimeS,distanceM,heightGainM,landSpeed,landVelY,landUpDot,upright,hpLost,destroyed"); } streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:yyyy-MM-dd HH:mm:ss},{1},{2},{3},{4:0.#},{5:0.#},{6:0.#},{7:0.##},{8:0.##},{9},{10},{11:0.###},{12:0.##},{13:0.##},{14:0.##},{15:0.##},{16:0.###},{17},{18},{19}", DateTime.Now, moon, type, _driver, _launchPos.x, _launchPos.y, _launchPos.z, _launchSpeed, _launchVelY, _launchGear, turboOffset.HasValue ? turboOffset.Value.ToString("0.###", CultureInfo.InvariantCulture) : "", airtime, distance, heightGain, ((Vector3)(ref landVel)).magnitude, landVel.y, upDot, upright ? 1 : 0, hpLost, destroyed ? 1 : 0)); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JumpTracker.AppendCsv] {arg}"); } } internal static void ReportJumps() { try { if (!File.Exists(CsvPath)) { ChatUtil.Send("No jumps recorded yet — get some air first."); return; } List<(string, float, bool, float, float, bool, int, bool)> list = new List<(string, float, bool, float, float, bool, int, bool)>(); foreach (string item in File.ReadAllLines(CsvPath).Skip(1)) { string[] array = item.Split(','); if (array.Length >= 20) { bool flag = array[10].Length > 0; list.Add((array[2], flag ? float.Parse(array[10], CultureInfo.InvariantCulture) : 0f, flag, float.Parse(array[11], CultureInfo.InvariantCulture), float.Parse(array[12], CultureInfo.InvariantCulture), array[17] == "1", int.Parse(array[18]), array[19] == "1")); } } List<(string, float, bool, float, float, bool, int, bool)> list2 = list.Where<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.type == "JUMP").ToList(); if (list2.Count == 0) { ChatUtil.Send("Only drops recorded so far — no proper jumps yet."); return; } (string, float, bool, float, float, bool, int, bool) tuple = list2.OrderByDescending<(string, float, bool, float, float, bool, int, bool), float>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance).First(); int num = (int)(100f * (float)list2.Count<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.upright) / (float)list2.Count); int num2 = (int)(100f * (float)list2.Count<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.hpLost == 0 && !r.Rest.Item1) / (float)list2.Count); ChatUtil.Send($"Jumps on record: {list2.Count} ({_sessionJumps} this session) · best {tuple.Item5:0.#}m / {tuple.Item4:0.00}s air · {num}% upright · {num2}% damage-free."); List<(string, float, bool, float, float, bool, int, bool)> list3 = list2.Where<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.hasTurbo && !r.Rest.Item1).ToList(); List<(string, float, bool, float, float, bool, int, bool)> list4 = list2.Where<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => !r.hasTurbo && !r.Rest.Item1).ToList(); if (list3.Count >= 5) { List<(string, float, bool, float, float, bool, int, bool)> list5 = list3.OrderByDescending<(string, float, bool, float, float, bool, int, bool), float>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance).Take(Mathf.Max(2, list3.Count / 3)).ToList(); float num3 = list5.Average<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.turbo); float num4 = list5.Min<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.turbo); float num5 = list5.Max<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.turbo); ChatUtil.Send(string.Format("⭐ Your best turbo timing: {0}{1:0.00}s vs liftoff ", (num3 >= 0f) ? "+" : "", num3) + string.Format("(top {0} jumps span {1}{2:0.00}s to {3}{4:0.00}s). ", list5.Count, (num4 >= 0f) ? "+" : "", num4, (num5 >= 0f) ? "+" : "", num5) + "Negative = pressed before leaving the ground."); List<(string, float, bool, float, float, bool, int, bool)> list6 = list3.Where<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.turbo < 0f).ToList(); List<(string, float, bool, float, float, bool, int, bool)> list7 = list3.Where<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.turbo >= 0f).ToList(); if (list6.Count > 0 && list7.Count > 0) { ChatUtil.Send($"Turbo BEFORE liftoff: avg {list6.Average<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance):0.#}m (n={list6.Count}) · " + $"AFTER liftoff: avg {list7.Average<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance):0.#}m (n={list7.Count})."); } if (list4.Count > 0) { ChatUtil.Send($"No-turbo jumps average {list4.Average<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance):0.#}m (n={list4.Count}) — turbo gains you {list3.Average<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance) - list4.Average<(string, float, bool, float, float, bool, int, bool)>(((string type, float turbo, bool hasTurbo, float airtime, float distance, bool upright, int hpLost, bool destroyed) r) => r.distance):0.#}m on average."); } } else { ChatUtil.Send($"Turbo-timing model needs more data: {list3.Count} turbo jump(s) recorded, want 5+. Keep jumping with turbo."); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JumpTracker.ReportJumps] {arg}"); ChatUtil.Send("Jump report failed — see BepInEx log."); } } } internal static class KeySpy { private const float SpySeconds = 15f; private static bool _active; internal static void Run() { if (_active) { ChatUtil.Send("Key spy already running — press the key!"); return; } _active = true; ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(Routine()); } private static IEnumerator Routine() { List<InputAction> hooked = new List<InputAction>(); Action<CallbackContext> cb = delegate(CallbackContext ctx) { try { ManualLogSource logger = Plugin.Logger; string[] obj2 = new string[6] { "[KeySpy] '", null, null, null, null, null }; InputActionMap actionMap = ((CallbackContext)(ref ctx)).action.actionMap; obj2[1] = ((actionMap != null) ? actionMap.name : null); obj2[2] = "/"; obj2[3] = ((CallbackContext)(ref ctx)).action.name; obj2[4] = "' performed via "; InputControl control = ((CallbackContext)(ref ctx)).control; obj2[5] = ((control != null) ? control.path : null); logger.LogWarning((object)string.Concat(obj2)); } catch { } }; try { IngamePlayerSettings instance = IngamePlayerSettings.Instance; object obj; if (instance == null) { obj = null; } else { PlayerInput playerInput = instance.playerInput; obj = ((playerInput != null) ? playerInput.actions : null); } InputActionAsset val = (InputActionAsset)obj; if ((Object)(object)val == (Object)null) { ChatUtil.Send("No input actions found (no player yet?)."); _active = false; yield break; } foreach (InputAction item in val) { item.performed += cb; hooked.Add(item); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[KeySpy] {arg}"); _active = false; yield break; } ChatUtil.Send($"Key spy ON for {15f:0}s — press the mystery key now. Results: BepInEx log, [KeySpy] lines."); yield return (object)new WaitForSeconds(15f); foreach (InputAction item2 in hooked) { item2.performed -= cb; } _active = false; ChatUtil.Send("Key spy off — check the log."); } } internal static class MoonTool { internal static void Handle(string[] parts) { try { StartOfRound sor = StartOfRound.Instance; if (sor?.levels == null || sor.levels.Length == 0) { ChatUtil.Send("No moon list yet."); return; } if (parts.Length < 3) { ChatUtil.Send("Moons: " + string.Join(", ", from l in sor.levels where (Object)(object)l != (Object)null select ((l.levelID == sor.currentLevelID) ? "> " : "") + Short(l.PlanetName)) + " · Usage: /bb moon <name> [seed]"); return; } string[] array = parts.Skip(2).ToArray(); int num = -1; if (array.Length >= 2 && int.TryParse(array[^1], out var result) && result >= 0) { num = result; array = array.Take(array.Length - 1).ToArray(); } string text = string.Join(" ", array); SelectableLevel val = Find(sor, text); if ((Object)(object)val == (Object)null) { ChatUtil.Send("No moon called \"" + text + "\". Try /bb moon for the list."); return; } if (!sor.inShipPhase || sor.travellingToNewLevel || sor.shipIsLeaving) { ChatUtil.Send("Routing only works in orbit."); return; } bool flag = (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost; if (num >= 0) { if (!flag) { ChatUtil.Send($"Routing to {Short(val.PlanetName)}; the seed has to be set by the host (/bb seed {num})."); } else { sor.overrideRandomSeed = true; sor.overrideSeedNumber = num; Plugin.Logger.LogInfo((object)$"[MoonTool] seed forced to {num}."); } } if (sor.currentLevelID == val.levelID) { ChatUtil.Send("Already at " + Short(val.PlanetName) + ((num >= 0 && flag) ? $", seed {num} for the next landing." : ".")); } else { ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(Route(sor, val, num, flag)); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[MoonTool.Handle] {arg}"); ChatUtil.Send("Couldn't route — see the BepInEx log."); } } private static IEnumerator Route(StartOfRound sor, SelectableLevel level, int seed, bool host) { Terminal val = Object.FindObjectOfType<Terminal>(); int credits = (((Object)(object)val != (Object)null) ? val.groupCredits : 0); sor.ChangeLevelServerRpc(level.levelID, credits); float until = Time.realtimeSinceStartup + 15f; while (sor.currentLevelID != level.levelID && Time.realtimeSinceStartup < until) { yield return (object)new WaitForSecondsRealtime(0.25f); } if (sor.currentLevelID != level.levelID) { ChatUtil.Send("The ship didn't take the route to " + Short(level.PlanetName) + "."); Plugin.Logger.LogWarning((object)$"[MoonTool] route to {level.levelID} didn't take."); yield break; } ChatUtil.Send("Routed to " + Short(level.PlanetName) + " for free" + ((seed >= 0 && host) ? $", seed {seed}" : "") + $" (credits still ${credits}).", broadcast: true); Plugin.Logger.LogInfo((object)$"[MoonTool] routed to {level.PlanetName} (id {level.levelID}), credits untouched."); } private static SelectableLevel Find(StartOfRound sor, string name) { string q = Squash(name); return ((IEnumerable<SelectableLevel>)sor.levels).FirstOrDefault((Func<SelectableLevel, bool>)((SelectableLevel l) => (Object)(object)l != (Object)null && Squash(Short(l.PlanetName)) == q)) ?? ((IEnumerable<SelectableLevel>)sor.levels).FirstOrDefault((Func<SelectableLevel, bool>)((SelectableLevel l) => (Object)(object)l != (Object)null && Squash(l.PlanetName).Contains(q))); } private static string Short(string planet) { if (string.IsNullOrEmpty(planet)) { return planet; } int num = planet.IndexOf(' '); if (num <= 0 || !char.IsDigit(planet[0])) { return planet; } return planet.Substring(num + 1); } private static string Squash(string s) { return new string((s ?? "").ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray()); } } internal static class RecallService { internal static void Log(string objectToken, string kind, string name) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) try { if (!ValidateKind(kind)) { return; } string text = CurrentPlanet(); if (text == null) { ChatUtil.Send("Not on a moon yet."); return; } string text2 = NormalizeObject(objectToken); Vector3 position; Vector3 eulerAngles; string text3; if (!(text2 == "me")) { if (!(text2 == "cruiser")) { ChatUtil.Send("Unknown object \"" + objectToken + "\" — use me or cruiser."); return; } VehicleController val = Object.FindObjectOfType<VehicleController>(); if ((Object)(object)val == (Object)null) { ChatUtil.Send("No cruiser found."); return; } position = ((Component)val).transform.position; eulerAngles = ((Component)val).transform.eulerAngles; text3 = "cruiser"; } else { PlayerControllerB val2 = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val2 == (Object)null) { ChatUtil.Send("No local player found."); return; } position = ((Component)val2).transform.position; eulerAngles = ((Component)val2).transform.eulerAngles; text3 = "me"; } bool flag = VarStore.Set(new RecallVar { Kind = kind, Name = name, SourceObject = text3, Planet = text, Pos = position, Rot = eulerAngles }); ChatUtil.Send((flag ? "Updated" : "Stored") + " pos " + name + ": " + text3 + " @ " + text + " (" + VarStore.FormatVec3(position) + ")."); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[RecallService.Log] {arg}"); } } internal static void Move(string objectToken, string kind, string name) { try { if (!ValidateKind(kind)) { return; } RecallVar recallVar = VarStore.Get(kind, name); if (recallVar == null) { ChatUtil.Send("No " + kind + " variable \"" + name + "\". /log list shows what's stored."); return; } string text = CurrentPlanet(); if (text == null) { ChatUtil.Send("Not on a moon yet."); return; } if (!string.Equals(text, recallVar.Planet, StringComparison.OrdinalIgnoreCase)) { ChatUtil.Send("pos " + recallVar.Name + " was recorded on " + recallVar.Planet + " — you're on " + text + ". Refusing (terrain differs)."); return; } string text2 = NormalizeObject(objectToken); if (!(text2 == "me")) { if (text2 == "cruiser") { MoveCruiser(recallVar); } else { ChatUtil.Send("Unknown object \"" + objectToken + "\" — use me or cruiser."); } } else { MovePlayer(recallVar); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[RecallService.Move] {arg}"); } } private static void MovePlayer(RecallVar v) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { ChatUtil.Send("No local player found."); return; } val.TeleportPlayer(v.Pos, true, v.Rot.y, false, true); ChatUtil.Send("Moved you to pos " + v.Name + " (" + VarStore.FormatVec3(v.Pos) + ")."); } private static void MoveCruiser(RecallVar v) { VehicleController val = Object.FindObjectOfType<VehicleController>(); if ((Object)(object)val == (Object)null) { ChatUtil.Send("No cruiser found."); } else if (val.carDestroyed) { ChatUtil.Send("The cruiser is destroyed."); } else if (val.magnetedToShip) { ChatUtil.Send("The cruiser is magnetted to the ship — detach it first."); } else if ((Object)(object)val.currentDriver != (Object)null || (Object)(object)val.currentPassenger != (Object)null) { ChatUtil.Send("Someone is in the cruiser — teleporting it out from under them would end badly."); } else { ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(TeleportCruiser(val, v)); } } private static IEnumerator TeleportCruiser(VehicleController vehicle, RecallVar v) { Quaternion rot = Quaternion.Euler(v.Rot); Rigidbody main = vehicle.mainRigidbody; bool wasKinematic = (Object)(object)main != (Object)null && main.isKinematic; NetworkObject component = ((Component)vehicle).GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null && !component.IsOwnedByServer) { component.ChangeOwnership(0uL); ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)"[Recall] reclaimed cruiser ownership from a client before teleport."); } } Rigidbody[] bodies = ((Component)vehicle).GetComponentsInChildren<Rigidbody>(); Rigidbody[] array = bodies; foreach (Rigidbody val in array) { if ((Object)(object)val != (Object)null && !val.isKinematic) { val.velocity = Vector3.zero; val.angularVelocity = Vector3.zero; } } if ((Object)(object)main != (Object)null) { main.isKinematic = true; } ((Component)vehicle).transform.SetPositionAndRotation(v.Pos, rot); Physics.SyncTransforms(); yield return (object)new WaitForFixedUpdate(); yield return (object)new WaitForFixedUpdate(); Rigidbody[] array2 = bodies; foreach (Rigidbody val2 in array2) { if ((Object)(object)val2 != (Object)null && !val2.isKinematic) { val2.velocity = Vector3.zero; val2.angularVelocity = Vector3.zero; } } if ((Object)(object)main != (Object)null && !wasKinematic) { main.isKinematic = false; main.velocity = Vector3.zero; main.angularVelocity = Vector3.zero; } vehicle.syncedPosition = v.Pos; vehicle.syncedRotation = rot; vehicle.SyncCarPositionClientRpc(v.Pos, v.Rot, 0f, 0f); ChatUtil.Send("Moved the cruiser to pos " + v.Name + " (" + VarStore.FormatVec3(v.Pos) + ")."); } internal static void Delete(string kind, string name) { try { if (ValidateKind(kind)) { if (VarStore.Remove(kind, name)) { ChatUtil.Send("Deleted " + kind + " " + name + "."); } else { ChatUtil.Send("No " + kind + " variable \"" + name + "\"."); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[RecallService.Delete] {arg}"); } } internal static void List() { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) try { List<RecallVar> list = (from v in VarStore.All orderby v.Kind, v.Name select v).ToList(); if (list.Count == 0) { ChatUtil.Send("Nothing stored. Usage: /log <me|cruiser> pos <name>"); return; } foreach (RecallVar item in list) { ChatUtil.Send(item.Kind + " " + item.Name + " — " + item.SourceObject + " @ " + item.Planet + " (" + VarStore.FormatVec3(item.Pos) + ")"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[RecallService.List] {arg}"); } } private static bool ValidateKind(string kind) { if (kind == "pos") { return true; } ChatUtil.Send("Unknown property \"" + kind + "\" — supported: pos."); return false; } private static string NormalizeObject(string token) { switch ((token ?? "").ToLowerInvariant()) { case "me": case "player": return "me"; case "cruiser": case "truck": case "car": return "cruiser"; default: return token; } } private static string CurrentPlanet() { SelectableLevel val = StartOfRound.Instance?.currentLevel; if (!((Object)(object)val != (Object)null) || string.IsNullOrEmpty(val.PlanetName)) { return null; } return val.PlanetName; } } internal static class SeedTool { private const int AnyInterior = int.MinValue; internal static void Handle(string[] parts) { try { if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsHost) { ChatUtil.Send("/bb seed is host-only (the host picks the seed)."); return; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { ChatUtil.Send("No lobby."); return; } string text = ((parts.Length >= 3) ? parts[2].ToLowerInvariant() : ""); switch (text) { case "": ChatUtil.Send($"Last seed: {instance.randomMapSeed}. Next landing: " + (instance.overrideRandomSeed ? $"FORCED to {instance.overrideSeedNumber}." : "random.") + " Usage: /bb seed <number|off> · /bb seed scan <moon> <interior|any> [count] [scrap n] [single [item]] [infest [nut|bug]] [fog] [meteor] [tries n] [from n|random]"); return; case "off": instance.overrideRandomSeed = false; ChatUtil.Send("Seed override OFF. Landings use random seeds again."); return; case "scan": Scan(parts); return; case "dump": Dump(); return; } if (int.TryParse(text, out var result) && result >= 0) { instance.overrideRandomSeed = true; instance.overrideSeedNumber = result; ChatUtil.Send($"Next landings are FORCED to seed {result} until /bb seed off. " + "Weather isn't part of it: it was rolled in orbit already."); Plugin.Logger.LogInfo((object)$"[SeedTool] override seed {result}."); } else { ChatUtil.Send("Usage: /bb seed <number|off> · /bb seed scan <moon> <interior|any> [count] [scrap n] [single [item]] [infest [nut|bug]] [fog] [meteor] [tries n] [from n|random]"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SeedTool.Handle] {arg}"); ChatUtil.Send("Seed command failed. See BepInEx log."); } } private static void Dump() { //IL_03ca: Unknown result type (might be due to invalid IL or missing references) try { StartOfRound instance = StartOfRound.Instance; RoundManager instance2 = RoundManager.Instance; if (instance?.levels == null || (Object)(object)instance2 == (Object)null) { ChatUtil.Send("Game not ready."); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# SimpleBlackBox moon dump (v81). One block per moon."); SelectableLevel[] levels = instance.levels; foreach (SelectableLevel val in levels) { if ((Object)(object)val == (Object)null) { continue; } stringBuilder.AppendLine($"MOON\t{val.levelID}\t{val.PlanetName}\tscrap {val.minScrap}-{val.maxScrap}\t" + $"value {val.minTotalScrapValue}-{val.maxTotalScrapValue}"); IntWithRarity[] array = (IntWithRarity[])(((object)val.dungeonFlowTypes) ?? ((object)new IntWithRarity[0])); foreach (IntWithRarity val2 in array) { stringBuilder.AppendLine($" INTERIOR\t{val2.id}\t{FlowName(val2.id)}\trarity {val2.rarity}"); } foreach (SpawnableItemWithRarity item in val.spawnableScrap ?? new List<SpawnableItemWithRarity>()) { if ((Object)(object)item?.spawnableItem != (Object)null) { stringBuilder.AppendLine($" SCRAP\t{item.spawnableItem.itemName}\trarity {item.rarity}\t" + $"value {item.spawnableItem.minValue}-{item.spawnableItem.maxValue}\ttwoHanded {item.spawnableItem.twoHanded}"); } } foreach (SpawnableEnemyWithRarity item2 in val.Enemies ?? new List<SpawnableEnemyWithRarity>()) { if ((Object)(object)item2?.enemyType != (Object)null) { stringBuilder.AppendLine($" INSIDE\t{item2.enemyType.enemyName}\trarity {item2.rarity}\tpower {item2.enemyType.PowerLevel}"); } } foreach (SpawnableEnemyWithRarity item3 in val.OutsideEnemies ?? new List<SpawnableEnemyWithRarity>()) { if ((Object)(object)item3?.enemyType != (Object)null) { stringBuilder.AppendLine($" OUTSIDE\t{item3.enemyType.enemyName}\trarity {item3.rarity}\tpower {item3.enemyType.PowerLevel}"); } } foreach (SpawnableEnemyWithRarity item4 in val.DaytimeEnemies ?? new List<SpawnableEnemyWithRarity>()) { if ((Object)(object)item4?.enemyType != (Object)null) { stringBuilder.AppendLine($" DAYTIME\t{item4.enemyType.enemyName}\trarity {item4.rarity}\tpower {item4.enemyType.PowerLevel}"); } } RandomWeatherWithVariables[] array2 = (RandomWeatherWithVariables[])(((ob