using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CG.Game.Player; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ResourceAssets; using UI.MainHUD; using UnityEngine; using UnityEngine.UIElements; using VoidManager; using VoidManager.CustomGUI; using VoidManager.MPModChecks; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Speedometer")] [assembly: AssemblyDescription("Adds a third HUD readout, in the same style as the O2 and Temperature widgets, showing your current speed in m/s.")] [assembly: AssemblyProduct("Speedometer")] [assembly: AssemblyCompany("Jack")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: ComVisible(false)] [assembly: AssemblyMetadata("ModCategories", "Quality of Life, Client-Side")] [assembly: AssemblyMetadata("MPType", "Client")] [assembly: AssemblyMetadata("ProgressionFlag", "enabled")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] [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; } } } internal static class MenuKit { private static readonly Dictionary foldouts = new Dictionary(); private static readonly HashSet loggedFailures = new HashSet(); public static void Header(string text) { GUILayout.Space(10f); GUILayout.Label("" + text + "", Array.Empty()); } public static void Rule() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(6f); Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); Color color = GUI.color; GUI.color = new Color(1f, 1f, 1f, 0.25f); GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture); GUI.color = color; GUILayout.Space(6f); } public static bool Foldout(string id, string label) { foldouts.TryGetValue(id, out var value); if (GUILayout.Button((value ? "[-] " : "[+] ") + label, Array.Empty())) { value = !value; foldouts[id] = value; } return value; } public static void DevZone(string id, Action body) { if (!Configs.IsDebugMode) { return; } GUILayout.Space(12f); Rule(); if (!Foldout(id + ".dev", "Developer tools")) { return; } GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); try { body(); } catch (Exception ex) { GUILayout.Label("Dev section failed: " + ex.Message + " (see log)", Array.Empty()); if (loggedFailures.Add(id)) { Debug.LogError((object)("[MenuKit] dev zone '" + id + "' threw: " + ex)); } } finally { GUILayout.EndVertical(); } } } internal static class ModFlagPolicy { internal const bool FlagsSession = true; internal const MultiplayerType MPType = (MultiplayerType)20; internal const SessionModificationEffect NetworkSpawnedEffect = (SessionModificationEffect)2; internal const SessionModificationEffect RequiredEffect = (SessionModificationEffect)94; internal const string BuildTag = "enabled"; } internal static class MutatorSlots { private const string TargetType = "Gameplay.Hub.HubMutatorManager"; private const string TargetMethod = "GetMaxMutatorsCount"; internal static ConfigEntry ExtraSlot; internal static bool Installed; internal static string Status = "not installed"; private static ManualLogSource _log; private static string _tag; internal static int Bonus { get { try { return (ExtraSlot != null && ExtraSlot.Value) ? 1 : 0; } catch { return 0; } } } internal static void Install(ConfigFile config, Harmony harmony, ManualLogSource log, string tag) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown _log = log; _tag = tag; try { ExtraSlot = config.Bind("Mutators", "ExtraMutatorSlot", true, "Raise the Mutators menu's selection limit by one, so this mod's mutator does not cost one of the vanilla slots. Every installed mod with this setting on adds its own +1. The limit is checked by the host, so the host's setting is the one that counts in a multiplayer room. Takes effect immediately."); if (!Installed) { Type type = AccessTools.TypeByName("Gameplay.Hub.HubMutatorManager"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "GetMaxMutatorsCount", (Type[])null, (Type[])null)); if (methodInfo == null) { Status = "FAILED - Gameplay.Hub.HubMutatorManager.GetMaxMutatorsCount not found (game update?)"; log.LogError((object)(tag + ": extra mutator slot not installed: " + Status)); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(MutatorSlots), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Installed = true; Status = "installed"; } } } catch (Exception ex) { Status = "FAILED - " + ex.GetType().Name; log.LogError((object)(tag + ": extra mutator slot not installed, vanilla limit stays: " + ex)); } } private static void Postfix(ref int __result) { try { __result += Bonus; } catch (Exception ex) { if (_log != null) { _log.LogWarning((object)(_tag + ": extra mutator slot postfix failed: " + ex)); } } } internal static void Draw() { if (ExtraSlot == null) { GUILayout.Label("Extra slot : " + Status, Array.Empty()); return; } bool flag = GUILayout.Toggle(ExtraSlot.Value, " Extra mutator slot (+1 to the selection limit; host's setting counts)", Array.Empty()); if (flag != ExtraSlot.Value) { ExtraSlot.Value = flag; } if (!Installed) { GUILayout.Label("Extra slot : " + Status, Array.Empty()); } } } internal static class StatContrib { internal enum Kind { Additive, Multiplicative, Override } private const string SlotKey = "jack.voidcrew.statcontrib.v1"; internal static ManualLogSource Log; private static object[] Root() { object[] array = AppDomain.CurrentDomain.GetData("jack.voidcrew.statcontrib.v1") as object[]; if (array == null) { array = new object[2] { new object(), new Dictionary() }; AppDomain.CurrentDomain.SetData("jack.voidcrew.statcontrib.v1", array); } return array; } internal static bool Set(object target, string field, string ownerId, Func getter, Action setter, Kind kind, float value, Func alive = null) { if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId) || getter == null || setter == null) { Error("StatContrib.Set called with a null argument (field '" + field + "', owner '" + ownerId + "') - ignored."); return false; } if (float.IsNaN(value) || float.IsInfinity(value)) { Error("StatContrib.Set: owner '" + ownerId + "' offered a non-finite value for '" + field + "' - ignored."); return false; } try { object[] array = Root(); lock (array[0]) { Dictionary dictionary = (Dictionary)array[1]; object value2; Dictionary dictionary2 = (Dictionary)(dictionary.TryGetValue(target, out value2) ? ((Dictionary)value2) : (dictionary[target] = new Dictionary())); object[] array2; if (dictionary2.TryGetValue(field, out value2)) { array2 = (object[])value2; } else { array2 = (object[])(dictionary2[field] = new object[4] { getter(), getter, setter, new List() }); Info("captured base " + ((float)array2[0]).ToString("0.####") + " for " + Describe(target, field)); } List list = (List)array2[3]; object[] array3 = null; for (int i = 0; i < list.Count; i++) { if ((string)list[i][0] == ownerId) { array3 = list[i]; break; } } if (array3 == null) { array3 = new object[4] { ownerId, null, null, null }; list.Add(array3); } array3[1] = alive; array3[2] = (int)kind; array3[3] = value; Recompute(dictionary2, field, array2); SweepDeadOwners(dictionary, target, field); } return true; } catch (Exception ex) { Error("StatContrib.Set('" + field + "', '" + ownerId + "') threw: " + ex); return false; } } internal static void Remove(object target, string field, string ownerId) { if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId)) { return; } try { object[] array = Root(); lock (array[0]) { Dictionary dictionary = (Dictionary)array[1]; if (!dictionary.TryGetValue(target, out var value)) { return; } Dictionary dictionary2 = (Dictionary)value; if (!dictionary2.TryGetValue(field, out value)) { return; } object[] array2 = (object[])value; List list = (List)array2[3]; bool flag = false; for (int num = list.Count - 1; num >= 0; num--) { if ((string)list[num][0] == ownerId) { list.RemoveAt(num); flag = true; } } if (flag) { Recompute(dictionary2, field, array2); if (!dictionary2.ContainsKey(field) && dictionary2.Count == 0) { dictionary.Remove(target); } SweepDeadOwners(dictionary, target, field); } } } catch (Exception ex) { Error("StatContrib.Remove('" + field + "', '" + ownerId + "') threw: " + ex); } } internal static string DescribeField(object target, string field) { try { object[] array = Root(); lock (array[0]) { Dictionary dictionary = (Dictionary)array[1]; if (target == null || !dictionary.TryGetValue(target, out var value)) { return null; } if (!((Dictionary)value).TryGetValue(field, out value)) { return null; } object[] array2 = (object[])value; List list = (List)array2[3]; string text = field + ": base " + ((float)array2[0]).ToString("0.####"); for (int i = 0; i < list.Count; i++) { text = text + ", " + (string)list[i][0] + " " + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + " " + ((float)list[i][3]).ToString("0.####"); } return text; } } catch { return null; } } private static void Recompute(Dictionary byField, string field, object[] entry) { List list = (List)entry[3]; for (int num = list.Count - 1; num >= 0; num--) { Func func = list[num][1] as Func; bool flag = true; if (func != null) { try { flag = func(); } catch { flag = false; } } if (!flag) { Info("dropping contribution '" + (string)list[num][0] + "' to '" + field + "' - its owner is gone."); list.RemoveAt(num); } } float num2 = (float)entry[0]; Action setter = (Action)entry[2]; if (list.Count == 0) { byField.Remove(field); TryWrite(setter, num2, field, "base restore"); return; } float num3 = num2; float num4 = 1f; object[] array = null; for (int i = 0; i < list.Count; i++) { float num5 = (float)list[i][3]; switch ((Kind)(int)list[i][2]) { case Kind.Additive: num3 += num5; break; case Kind.Multiplicative: num4 *= num5; break; case Kind.Override: array = list[i]; break; } } float num6; if (array != null) { num6 = (float)array[3]; if (list.Count > 1) { Info("override '" + (string)array[0] + "' wins '" + field + "' over " + (list.Count - 1) + " other contribution(s)."); } } else { num6 = num3 * num4; } if (float.IsNaN(num6) || float.IsInfinity(num6)) { Error("composed value for '" + field + "' is non-finite; writing the base back instead. " + DescribeEntry(field, entry)); num6 = num2; } TryWrite(setter, num6, field, "recompute"); } private static void SweepDeadOwners(Dictionary entries, object exceptTarget, string exceptField) { List list = null; foreach (KeyValuePair entry in entries) { Dictionary dictionary = (Dictionary)entry.Value; List list2 = null; foreach (KeyValuePair item in dictionary) { if (entry.Key == exceptTarget && item.Key == exceptField) { continue; } List list3 = (List)((object[])item.Value)[3]; for (int i = 0; i < list3.Count; i++) { Func func = list3[i][1] as Func; bool flag = true; if (func != null) { try { flag = func(); } catch { flag = false; } } if (!flag) { List obj2 = list2 ?? new List(); list2 = obj2; obj2.Add(item.Key); break; } } } if (list2 != null) { for (int j = 0; j < list2.Count; j++) { Recompute(dictionary, list2[j], (object[])dictionary[list2[j]]); } } if (dictionary.Count == 0) { List obj3 = list ?? new List(); list = obj3; obj3.Add(entry.Key); } } if (list != null) { for (int k = 0; k < list.Count; k++) { entries.Remove(list[k]); } } } private static void TryWrite(Action setter, float value, string field, string why) { try { setter(value); } catch (Exception ex) { Info("write to '" + field + "' (" + why + ") failed: " + ex.Message); } } private static string Describe(object target, string field) { string text; try { text = target.GetType().Name; } catch { text = ""; } return text + "." + field; } private static string DescribeEntry(string field, object[] entry) { List list = (List)entry[3]; string text = "base " + ((float)entry[0]).ToString("0.####") + ";"; for (int i = 0; i < list.Count; i++) { text = text + " " + (string)list[i][0] + "=" + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + ":" + ((float)list[i][3]).ToString("0.####"); } return text; } private static void Info(string m) { if (Log != null) { Log.LogInfo((object)("StatContrib: " + m)); } } private static void Error(string m) { if (Log != null) { Log.LogError((object)("StatContrib: " + m)); } } } internal interface ITuningKnob { string Name { get; } string Help { get; } bool IsInert { get; } void WriteJson(JObject o); void ReadJson(JObject o); string Describe(); void DrawMenuRow(); string BakeSnippet(); } internal sealed class TuningFile { private readonly string _guid; private readonly string _tag; private readonly string _readme; private readonly ManualLogSource _log; private readonly List _order = new List(); private readonly Dictionary _byName = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly List _reloadHandlers = new List(); private DateTime _lastWrite = DateTime.MinValue; private JObject _baseline; private JObject _previousBaseline; internal string LastBakePath = ""; private string _menuSignature; internal string Status { get; private set; } = "not loaded"; internal string Path { get; private set; } internal string BaselinePath { get; private set; } internal IList Knobs => _order; internal bool AllInert { get { for (int i = 0; i < _order.Count; i++) { if (!_order[i].IsInert) { return false; } } return true; } } internal TuningFile(string pluginGuid, string logTag, string readme, ManualLogSource log) { _guid = pluginGuid; _tag = logTag; _readme = readme; _log = log; } internal T Add(T knob) where T : ITuningKnob { if (knob == null) { return default(T); } if (_byName.ContainsKey(knob.Name)) { Warn("duplicate tuning knob '" + knob.Name + "' ignored. Two declarations of one name means one of them is silently dead."); return knob; } _byName[knob.Name] = knob; _order.Add(knob); return knob; } internal ITuningKnob Find(string name) { if (!_byName.TryGetValue(name, out var value)) { return null; } return value; } internal void OnReload(Action handler) { if (handler != null && !_reloadHandlers.Contains(handler)) { _reloadHandlers.Add(handler); } } internal void Initialise(string configDir) { try { Path = System.IO.Path.Combine(configDir, _guid + ".tuning.json"); BaselinePath = System.IO.Path.Combine(configDir, _guid + ".tuning.baseline.json"); _baseline = BuildKnobObject(); _previousBaseline = ReadPreviousBaseline(); WriteBaseline(); if (!File.Exists(Path)) { Save("created at the declared defaults"); } else { Load(); } } catch (Exception e) { Status = "FAILED to initialise: " + Brief(e); Error(Status); } } internal bool PollForChanges() { if (string.IsNullOrEmpty(Path)) { return false; } try { if (!File.Exists(Path)) { return false; } if (File.GetLastWriteTimeUtc(Path) <= _lastWrite) { return false; } Load(); Info("tuning reloaded - " + Status); FireReloadHandlers(); return true; } catch (Exception e) { Warn("tuning poll threw: " + Brief(e)); return false; } } private void FireReloadHandlers() { for (int i = 0; i < _reloadHandlers.Count; i++) { try { _reloadHandlers[i](); } catch (Exception ex) { Error("a tuning re-apply handler threw: " + ex); } } } private void Load() { string text; try { text = File.ReadAllText(Path); } catch (Exception e) { Status = "could not read the file (" + Brief(e) + "); previous values stand"; Error(Status); return; } _lastWrite = File.GetLastWriteTimeUtc(Path); JObject val; try { val = JObject.Parse(text); } catch (Exception e2) { Status = "MALFORMED tuning file (" + Brief(e2) + "); previous values stand"; Error(Status + ". Fix " + Path + " - nothing was applied from it."); return; } JToken obj = val["knobs"]; JObject val2 = (JObject)(object)((obj is JObject) ? obj : null); if (val2 == null) { Status = "tuning file has no 'knobs' object; every knob left at its declared default"; Warn(Status); return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; foreach (KeyValuePair item in val2) { if (!_byName.TryGetValue(item.Key, out var value)) { num2++; Warn("tuning file names unknown knob '" + item.Key + "' - ignored. This is a typo or a rename, not a tuning value."); continue; } JToken value2 = item.Value; JObject val3 = (JObject)(object)((value2 is JObject) ? value2 : null); if (val3 == null) { continue; } if (FollowsNewDeclaration(item.Key, val3)) { num4++; num++; continue; } try { value.ReadJson(val3); } catch (Exception e3) { Warn("knob '" + item.Key + "' could not be read: " + Brief(e3)); } num++; if (!value.IsInert) { num3++; } } Status = num + " knob(s) read, " + num3 + " active" + ((num2 > 0) ? (", " + num2 + " unknown name(s) ignored") : "") + ((num4 > 0) ? (", " + num4 + " untouched knob(s) moved to this build's new declared value") : ""); if (num3 == 0) { Status += " - every knob inert"; } int count = DriftReport().Count; if (count > 0) { Status = Status + "; " + count + " differ(s) from source"; } if (num2 > 0 || num < _order.Count || num4 > 0) { string status = Status; Save("rewritten to match this version's knobs (" + (_order.Count - num) + " added, " + num2 + " stale name(s) dropped)"); Status = status + "; file rewritten to match this version's knobs"; } } private JObject ReadPreviousBaseline() { try { if (!File.Exists(BaselinePath)) { return null; } JToken obj = JObject.Parse(File.ReadAllText(BaselinePath))["knobs"]; return (JObject)(object)((obj is JObject) ? obj : null); } catch { return null; } } private bool FollowsNewDeclaration(string name, JObject fromFile) { if (_previousBaseline == null || _baseline == null) { return false; } JToken obj = _previousBaseline[name]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); JToken obj2 = _baseline[name]; JObject val2 = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val == null || val2 == null) { return false; } if (JToken.DeepEquals((JToken)(object)val, (JToken)(object)val2)) { return false; } return JToken.DeepEquals((JToken)(object)val, (JToken)(object)fromFile); } internal void Save(string why) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown try { JObject val = new JObject(); val["_readme"] = JToken.op_Implicit(_readme + " The '_' field in each knob is a comment and is ignored. Saved changes are picked up within a second - no restart. This file is NOT shipped: values dialled in here reach nobody else until they are written back into the mod's source declarations."); val["knobs"] = (JToken)(object)BuildKnobObject(); EnsureDir(Path); File.WriteAllText(Path, ((JToken)val).ToString((Formatting)1, Array.Empty())); _lastWrite = File.GetLastWriteTimeUtc(Path); Status = why; Info("tuning file " + why + " -> " + Path); } catch (Exception e) { Status = "could not write the tuning file: " + Brief(e); Error(Status); } } private JObject BuildKnobObject() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown JObject val = new JObject(); for (int i = 0; i < _order.Count; i++) { ITuningKnob tuningKnob = _order[i]; JObject val2 = new JObject(); val2["_"] = JToken.op_Implicit(tuningKnob.Help); tuningKnob.WriteJson(val2); val[tuningKnob.Name] = (JToken)(object)val2; } return val; } private void WriteBaseline() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown try { JObject val = new JObject(); val["_readme"] = JToken.op_Implicit("Generated every launch. These are the knob values as DECLARED IN SOURCE, which is what a build handed to anyone else runs at. Diff the sibling .tuning.json against this to see which dialled-in numbers would be lost by shipping. Do not edit; edit the source declarations and relaunch."); val["knobs"] = (JToken)(object)_baseline; EnsureDir(BaselinePath); File.WriteAllText(BaselinePath, ((JToken)val).ToString((Formatting)1, Array.Empty())); } catch (Exception e) { Warn("could not write the tuning baseline (" + Brief(e) + "). The release drift check has nothing to compare against for this mod."); } } internal List DriftReport() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown List list = new List(); if (_baseline == null) { return list; } for (int i = 0; i < _order.Count; i++) { ITuningKnob tuningKnob = _order[i]; JObject val = new JObject(); tuningKnob.WriteJson(val); JToken obj = _baseline[tuningKnob.Name]; JObject val2 = (JObject)(object)((obj is JObject) ? obj : null); if (val2 == null) { continue; } foreach (KeyValuePair item in val) { JToken val3 = val2[item.Key]; if (val3 != null && !JToken.DeepEquals(val3, item.Value)) { list.Add(tuningKnob.Name + "." + item.Key + ": source " + ((object)val3)?.ToString() + " -> live " + (object)item.Value); } } } return list; } internal string WriteBakeSnippet(string outputDir) { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("// " + _tag + " - tuning bake, taken from the live tuning file."); stringBuilder.AppendLine("//"); stringBuilder.AppendLine("// Paste over the knob declarations. After this the declared defaults"); stringBuilder.AppendLine("// and the tuning file agree, the drift check is clean, and a build"); stringBuilder.AppendLine("// handed to anyone else plays at the numbers you dialled in."); stringBuilder.AppendLine("//"); stringBuilder.AppendLine("// Source : " + (Path ?? "")); stringBuilder.AppendLine("// State : " + Status); stringBuilder.AppendLine(); for (int i = 0; i < _order.Count; i++) { stringBuilder.AppendLine(_order[i].BakeSnippet()); } List list = DriftReport(); stringBuilder.AppendLine(); if (list.Count == 0) { stringBuilder.AppendLine("// Nothing has drifted - source already matches the tuning file."); } else { stringBuilder.AppendLine("// Differs from source (" + list.Count + "):"); for (int j = 0; j < list.Count; j++) { stringBuilder.AppendLine("// " + list[j]); } } string text = System.IO.Path.Combine(outputDir, _guid + ".tuning.bake.cs.txt"); EnsureDir(text); File.WriteAllText(text, stringBuilder.ToString()); Info("tuning bake snippet -> " + text); return text; } catch (Exception ex) { Error("tuning bake snippet threw: " + ex); return ""; } } internal void DrawMenu() { try { GUILayout.Label("Tuning : " + Status, Array.Empty()); GUILayout.Label(" " + (Path ?? ""), Array.Empty()); for (int i = 0; i < _order.Count; i++) { _order[i].DrawMenuRow(); } string text = MenuSignature(); if (_menuSignature == null) { _menuSignature = text; } else if (text != _menuSignature) { _menuSignature = text; FireReloadHandlers(); } List list = DriftReport(); GUILayout.Space(6f); if (list.Count == 0) { GUILayout.Label("Source and tuning file agree - safe to package.", Array.Empty()); } else { GUILayout.Label("NOT IN SOURCE (" + list.Count + ") - a build sent to anyone else", Array.Empty()); GUILayout.Label("would run at the source values, not these:", Array.Empty()); for (int j = 0; j < list.Count; j++) { GUILayout.Label(" " + list[j], Array.Empty()); } } GUILayout.Space(4f); if (GUILayout.Button("Save these values to the tuning file", Array.Empty())) { Save("saved from the F5 menu"); } if (GUILayout.Button("Reload the tuning file now", Array.Empty())) { _lastWrite = DateTime.MinValue; PollForChanges(); } if (GUILayout.Button("Write the bake snippet (the release step)", Array.Empty())) { LastBakePath = WriteBakeSnippet(Paths.ConfigPath); } GUILayout.Label(" " + LastBakePath, Array.Empty()); } catch (Exception e) { GUILayout.Label("Tuning menu failed to draw: " + Brief(e), Array.Empty()); } } private string MenuSignature() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _order.Count; i++) { JObject val = new JObject(); _order[i].WriteJson(val); stringBuilder.Append(((JToken)val).ToString((Formatting)0, Array.Empty())); } return stringBuilder.ToString(); } private static void EnsureDir(string file) { string directoryName = System.IO.Path.GetDirectoryName(file); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } } private static string Brief(Exception e) { if (e != null) { return e.GetType().Name + ": " + e.Message; } return ""; } private void Info(string m) { if (_log != null) { _log.LogInfo((object)(_tag + ": " + m)); } } private void Warn(string m) { if (_log != null) { _log.LogWarning((object)(_tag + ": " + m)); } } private void Error(string m) { if (_log != null) { _log.LogError((object)(_tag + ": " + m)); } } internal static string F(float v) { return v.ToString("0.####", CultureInfo.InvariantCulture); } } internal sealed class TuningScalar : ITuningKnob { public float Value; public readonly float Declared; public readonly float Min; public readonly float Max; public readonly float Sentinel; private readonly NumericField _field = new NumericField(); public string Name { get; private set; } public string Help { get; private set; } public bool IsInert { get { if (!float.IsNaN(Sentinel) && Nearly(Value, Sentinel)) { return true; } return Nearly(Value, Declared); } } public bool IsSentinel { get { if (!float.IsNaN(Sentinel)) { return Nearly(Value, Sentinel); } return false; } } internal TuningScalar(string name, string help, float declared, float min, float max, float sentinel = float.NaN) { Name = name; Help = help; Value = declared; Declared = declared; Min = min; Max = max; Sentinel = sentinel; } public void WriteJson(JObject o) { o["value"] = JToken.op_Implicit(Value); } public void ReadJson(JObject o) { JToken val = o["value"]; if (val == null) { return; } try { float num = Extensions.Value((IEnumerable)val); if (!float.IsNaN(num) && !float.IsInfinity(num)) { Value = num; } } catch { } } public string Describe() { if (!IsSentinel) { return TuningFile.F(Value); } return "keep the game's own value"; } public void DrawMenuRow() { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(Name + " " + Describe(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) }); Value = _field.Draw(Value); Value = GUILayout.HorizontalSlider(Value, Min, Max, Array.Empty()); GUILayout.EndHorizontal(); } public string BakeSnippet() { string text = (float.IsNaN(Sentinel) ? "" : (", " + TuningFile.F(Sentinel) + "f")); return "Scalar(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + TuningFile.F(Value) + "f, " + TuningFile.F(Min) + "f, " + TuningFile.F(Max) + "f" + text + ");"; } private static bool Nearly(float a, float b) { return Math.Abs(a - b) < 0.0001f; } } internal sealed class TuningString : ITuningKnob { public string Value; public readonly string Declared; public string Name { get; private set; } public string Help { get; private set; } public bool HasValue => !string.IsNullOrEmpty(Value); public bool IsInert => string.Equals(Value, Declared, StringComparison.Ordinal); internal TuningString(string name, string help, string declared) { Name = name; Help = help; Value = declared ?? ""; Declared = declared ?? ""; } public void WriteJson(JObject o) { o["text"] = JToken.op_Implicit(Value ?? ""); } public void ReadJson(JObject o) { JToken val = o["text"]; if (val == null) { return; } try { string text = Extensions.Value((IEnumerable)val); Value = (text ?? "").Trim(); } catch { } } public string Describe() { if (!HasValue) { return "the game's own choice"; } return Value; } public void DrawMenuRow() { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) }); Value = GUILayout.TextField(Value ?? "", Array.Empty()) ?? ""; GUILayout.EndHorizontal(); } public string BakeSnippet() { return "Text(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", \"" + (Value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"") + "\");"; } } internal sealed class NumericField { private string _text; private float _synced = float.NaN; internal float Draw(float value, float width = 64f, bool integer = false) { if (_text == null || value != _synced) { _text = (integer ? ((int)Math.Round(value)).ToString(CultureInfo.InvariantCulture) : TuningFile.F(value)); _synced = value; } string text = GUILayout.TextField(_text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); if (text == _text) { return value; } _text = text; if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return value; } if (float.IsNaN(result) || float.IsInfinity(result)) { return value; } if (integer) { result = (float)Math.Round(result); } _synced = result; return result; } } internal sealed class TuningToggle : ITuningKnob { public bool Value; public readonly bool Declared; public string Name { get; private set; } public string Help { get; private set; } public bool IsInert => Value == Declared; internal TuningToggle(string name, string help, bool declared) { Name = name; Help = help; Value = declared; Declared = declared; } public void WriteJson(JObject o) { o["on"] = JToken.op_Implicit(Value); } public void ReadJson(JObject o) { JToken val = o["on"]; if (val == null) { return; } try { Value = Extensions.Value((IEnumerable)val); } catch { } } public string Describe() { if (!Value) { return "off"; } return "on"; } public void DrawMenuRow() { Value = GUILayout.Toggle(Value, " " + Name + " - " + Help, Array.Empty()); } public string BakeSnippet() { return "Toggle(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + (Value ? "true" : "false") + ");"; } } namespace Speedometer { [BepInPlugin("jack.voidcrew.speedometer", "Speedometer", "1.0.0")] [BepInProcess("Void Crew.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry ShowSpeedometer; internal static ConfigEntry RowGapPx; internal static ConfigEntry UseRollingAverage; internal static ConfigEntry RollingAverageTicks; private void Awake() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; ShowSpeedometer = ((BaseUnityPlugin)this).Config.Bind("UI", "ShowSpeedometer", true, "Show the speed (m/s) readout on the HUD, next to O2 and Temperature."); RowGapPx = ((BaseUnityPlugin)this).Config.Bind("UI", "RowGapPx", -1f, "Optional extra horizontal gap between the O2/Temperature/Speed row items, in pixels, on top of the default spacing. -1 (default) adds nothing extra. Takes effect live; no restart needed."); UseRollingAverage = ((BaseUnityPlugin)this).Config.Bind("UI", "UseRollingAverage", false, "Show a rolling average over the last RollingAverageTicks frames instead of the instantaneous speed."); RollingAverageTicks = ((BaseUnityPlugin)this).Config.Bind("UI", "RollingAverageTicks", 30, "Window size, in ticks (Update() frames), for the rolling average when UseRollingAverage is on. Clamped to [1, 600]. Higher = smoother but laggier."); GameObject val = new GameObject("Speedometer.Driver"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent(); Log.LogInfo((object)"Speedometer v1.0.0 loaded."); } } public class SpeedometerVoidPlugin : VoidPlugin { public override MultiplayerType MPType => (MultiplayerType)8; public override string Author => "Jack"; public override string Description => "Adds a third HUD readout, in the same style as the O2 and Temperature widgets, showing your current speed in m/s."; public override string ThunderstoreID => "Jack_Modding/Speedometer"; } public class SpeedometerSettingsMenu : ModSettingsMenu { private static string lastDump = ""; public override string Name() { return "Speedometer"; } public override void Draw() { Plugin.ShowSpeedometer.Value = GUILayout.Toggle(Plugin.ShowSpeedometer.Value, "Show speed (m/s) on the HUD", Array.Empty()); GUILayout.Space(6f); GUILayout.Label("Row gap override (px, -1 = leave authored spacing alone):", Array.Empty()); if (float.TryParse(GUILayout.TextField(Plugin.RowGapPx.Value.ToString("0.##"), Array.Empty()), out var result)) { Plugin.RowGapPx.Value = result; } GUILayout.Space(6f); Plugin.UseRollingAverage.Value = GUILayout.Toggle(Plugin.UseRollingAverage.Value, "Smooth reading: rolling average over the last N ticks (instead of instant)", Array.Empty()); if (Plugin.UseRollingAverage.Value) { GUILayout.Label("Window size N (ticks):", Array.Empty()); if (int.TryParse(GUILayout.TextField(Plugin.RollingAverageTicks.Value.ToString(), Array.Empty()), out var result2)) { Plugin.RollingAverageTicks.Value = Mathf.Clamp(result2, 1, 600); } } MenuKit.DevZone("speedometer", DrawDevTools); } private static void DrawDevTools() { if (GUILayout.Button("Dump HUD cluster to log (O2/Temperature/HP + our element)", Array.Empty())) { try { lastDump = SpeedometerHudController.DumpTree(); Plugin.Log.LogInfo((object)lastDump); } catch (Exception ex) { lastDump = "dump failed: " + ex.Message; Plugin.Log.LogError((object)("Speedometer: dump failed: " + ex)); } } if (!string.IsNullOrEmpty(lastDump)) { GUILayout.Label(" (full text is in BepInEx/LogOutput.log)", Array.Empty()); string[] array = lastDump.Split(new char[1] { '\n' }); int num = ((array.Length < 20) ? array.Length : 20); for (int i = 0; i < num; i++) { GUILayout.Label(array[i], Array.Empty()); } if (array.Length > num) { GUILayout.Label(" ... " + (array.Length - num) + " more", Array.Empty()); } } } } internal static class SpeedometerPluginInfo { public const string PLUGIN_GUID = "jack.voidcrew.speedometer"; public const string PLUGIN_NAME = "Speedometer"; public const string USERS_PLUGIN_NAME = "Speedometer"; public const string PLUGIN_VERSION = "1.0.0"; public const string PLUGIN_DESCRIPTION = "Adds a third HUD readout, in the same style as the O2 and Temperature widgets, showing your current speed in m/s."; public const string PLUGIN_AUTHORS = "Jack"; public const string PLUGIN_THUNDERSTORE_ID = "Jack_Modding/Speedometer"; } internal static class SpeedIcon { private const int FallbackSize = 64; private const string ResourceName = "Speedometer.assets.speedometer-icon.png"; private static Texture2D cached; private static bool buildFailed; internal static Texture2D GetTexture() { if ((Object)(object)cached != (Object)null || buildFailed) { return cached; } try { cached = LoadPackaged() ?? BuildFallback(); } catch (Exception ex) { buildFailed = true; Plugin.Log.LogWarning((object)("Speedometer: icon load failed, showing no glyph: " + ex)); } return cached; } private static Texture2D LoadPackaged() { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown Assembly executingAssembly = Assembly.GetExecutingAssembly(); string name = "Speedometer.assets.speedometer-icon.png"; bool flag = false; string[] manifestResourceNames = executingAssembly.GetManifestResourceNames(); for (int i = 0; i < manifestResourceNames.Length; i++) { if (string.Equals(manifestResourceNames[i], "Speedometer.assets.speedometer-icon.png", StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { manifestResourceNames = executingAssembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (text.EndsWith("speedometer-icon.png", StringComparison.OrdinalIgnoreCase)) { name = text; flag = true; break; } } } if (!flag) { Plugin.Log.LogWarning((object)"Speedometer: embedded icon resource not found - falling back to the procedural glyph."); return null; } byte[] array; using (Stream stream = executingAssembly.GetManifestResourceStream(name)) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = "SpeedometerIcon", filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; if (!ImageConversion.LoadImage(val, array)) { Plugin.Log.LogWarning((object)"Speedometer: could not decode embedded icon - falling back to the procedural glyph."); return null; } return val; } private static Texture2D BuildFallback() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false) { name = "SpeedometerIconFallback", filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; Vector2[][] array = new Vector2[2][]; for (int i = 0; i < 2; i++) { float num = 64f * ((i == 0) ? 0.46f : 0.74f); float num2 = 32f; float num3 = 14.08f; float num4 = 19.2f; array[i] = (Vector2[])(object)new Vector2[3] { new Vector2(num - num3, num2 - num4), new Vector2(num, num2), new Vector2(num - num3, num2 + num4) }; } Color32[] array2 = (Color32[])(object)new Color32[4096]; for (int j = 0; j < 64; j++) { for (int k = 0; k < 64; k++) { float px = (float)k + 0.5f; float py = (float)j + 0.5f; float num5 = float.MaxValue; Vector2[][] array3 = array; foreach (Vector2[] array4 in array3) { num5 = Mathf.Min(num5, DistanceToSegment(px, py, array4[0], array4[1])); num5 = Mathf.Min(num5, DistanceToSegment(px, py, array4[1], array4[2])); } float num6 = Mathf.Clamp01(0.5f - (num5 - 5.12f) / 1.5f); array2[j * 64 + k] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)Mathf.RoundToInt(num6 * 255f)); } } val.SetPixels32(array2); val.Apply(false, false); return val; } private static float DistanceToSegment(float px, float py, Vector2 a, Vector2 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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_0034: 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) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_0048: Unknown result type (might be due to invalid IL or missing references) Vector2 val = b - a; float num = Mathf.Clamp01(Vector2.Dot(new Vector2(px, py) - a, val) / Mathf.Max(((Vector2)(ref val)).sqrMagnitude, 0.0001f)); Vector2 val2 = a + val * num; return Vector2.Distance(new Vector2(px, py), val2); } } internal class SpeedometerDriver : MonoBehaviour { private void Update() { SpeedometerHudController.Tick(); } } internal static class SpeedometerHudController { private const string MarkerClass = "speedometer-resource"; private const string ElementName = "SpeedometerResource"; private const float FallbackGap = 6f; private static readonly FieldInfo ContainerF = AccessTools.Field(typeof(HUDElement), "container"); private static PlayerHUD hud; private static VisualElement container; private static VisualElement stackParent; private static VisualElement anchor; private static VisualElement oxygenAnchor; private static readonly VisualElement[] leftSpacers = (VisualElement[])(object)new VisualElement[2]; private static HUDResourceVE speedVE; private static bool usingFlexInsert; private static float nextSearchTime; private static float nextPlaceTime; private static float nextValueTime; private static int lastShownSpeed = int.MinValue; private static float appliedGapPx = float.NaN; private static float appliedEqualWidth = float.NaN; private static bool tickErrorLogged; private static bool templateWarned; private static bool anchorsWarned; private static float[] sampleBuffer; private static int sampleCount; private static int sampleIndex; private static float sampleSum; private static int appliedCapacity = -1; internal static void Tick() { try { TickInner(); } catch (Exception ex) { if (!tickErrorLogged) { tickErrorLogged = true; Plugin.Log.LogError((object)("Speedometer: tick error (ticks continue; logged once): " + ex)); } } } private static void TickInner() { if (!Plugin.ShowSpeedometer.Value) { Unwire(); return; } if (speedVE == null || (Object)(object)hud == (Object)null) { if (Time.unscaledTime < nextSearchTime) { return; } nextSearchTime = Time.unscaledTime + 1f; if (!TryWire()) { return; } } EqualizeWidths(); ApplyGapOverride(); EnsureSampleCapacity(); PushSample(ReadSpeed()); UpdateValue(); if (!usingFlexInsert) { Place(); } } private static bool TryWire() { //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) Unwire(); hud = Object.FindObjectOfType(); if ((Object)(object)hud == (Object)null) { return false; } object? value = ContainerF.GetValue(hud); container = (VisualElement)((value is VisualElement) ? value : null); if (container == null) { hud = null; return false; } VisualElement val = UQueryExtensions.Q(container, "OxygenResource", (string)null); VisualElement val2 = UQueryExtensions.Q(container, "TemperatureResource", (string)null); if (val == null || val2 == null) { if (!anchorsWarned) { anchorsWarned = true; Plugin.Log.LogWarning((object)"Speedometer: LocalPlayerContainer has no OxygenResource/TemperatureResource to anchor on (game update?). Icon stays hidden."); } hud = null; container = null; return false; } oxygenAnchor = val; anchor = val2; VisualElement obj = BuildElement(); speedVE = (HUDResourceVE)(object)((obj is HUDResourceVE) ? obj : null); if (speedVE == null) { hud = null; container = null; return false; } if (val2.parent != null && val2.parent == val.parent) { stackParent = val2.parent; stackParent.style.alignSelf = StyleEnum.op_Implicit((Align)1); BuildLeftSpacers(stackParent, val); int num = stackParent.IndexOf(val2); stackParent.Insert(num + 1, (VisualElement)(object)speedVE); usingFlexInsert = true; ((VisualElement)speedVE).style.position = StyleEnum.op_Implicit((Position)0); } else { container.Add((VisualElement)(object)speedVE); usingFlexInsert = false; ((VisualElement)speedVE).style.position = StyleEnum.op_Implicit((Position)1); } speedVE.Unit = " m/s"; SetIgnorePicking((VisualElement)(object)speedVE); ApplyIcon((VisualElement)(object)speedVE); appliedGapPx = float.NaN; appliedEqualWidth = float.NaN; lastShownSpeed = int.MinValue; tickErrorLogged = false; nextPlaceTime = 0f; nextValueTime = 0f; Plugin.Log.LogInfo((object)("Speedometer: HUD element built (" + (usingFlexInsert ? "inserted into the O2/Temperature stack" : "absolute-positioned fallback") + ").")); return true; } private static VisualElement BuildElement() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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) UIDocument component = ((Component)hud).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component.visualTreeAsset == (Object)null) { return null; } VisualElement val = UQueryExtensions.Q((VisualElement)(object)component.visualTreeAsset.Instantiate(), "LocalPlayerContainer", (string)null); VisualElement val2 = ((val != null) ? UQueryExtensions.Q(val, "TemperatureResource", (string)null) : null); if (val2 == null) { if (!templateWarned) { templateWarned = true; Plugin.Log.LogWarning((object)"Speedometer: the HUD document has no LocalPlayerContainer/TemperatureResource template to copy - element skipped this session."); } return null; } val2.RemoveFromHierarchy(); val2.name = "SpeedometerResource"; val2.AddToClassList("speedometer-resource"); val2.style.display = StyleEnum.op_Implicit((DisplayStyle)0); val2.style.visibility = StyleEnum.op_Implicit((Visibility)0); val2.style.opacity = StyleFloat.op_Implicit(1f); return val2; } private static void BuildLeftSpacers(VisualElement parent, VisualElement oxygenRes) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) VisualElement val = UQueryExtensions.Q(oxygenRes, "IconBackground", (string)null); if (val == null) { return; } int num = parent.IndexOf(oxygenRes); for (int i = 0; i < leftSpacers.Length; i++) { VisualElement val2 = new VisualElement { name = "SpeedometerLeftSpacer" + i }; foreach (string @class in val.GetClasses()) { val2.AddToClassList(@class); } val2.style.visibility = StyleEnum.op_Implicit((Visibility)1); val2.pickingMode = (PickingMode)1; parent.Insert(num, val2); leftSpacers[i] = val2; } } private static void ApplyIcon(VisualElement ve) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) VisualElement val = UQueryExtensions.Q(ve, "Icon", (string)null); if (val != null) { Texture2D texture = SpeedIcon.GetTexture(); if (!((Object)(object)texture == (Object)null)) { val.style.backgroundImage = new StyleBackground(texture); } } } private static void SetIgnorePicking(VisualElement ve) { ve.pickingMode = (PickingMode)1; foreach (VisualElement item in ve.Children()) { SetIgnorePicking(item); } } private static void EqualizeWidths() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (usingFlexInsert && oxygenAnchor != null && anchor != null && speedVE != null) { float width = oxygenAnchor.resolvedStyle.width; if (!float.IsNaN(width) && !(width <= 0f) && width != appliedEqualWidth) { appliedEqualWidth = width; anchor.style.width = StyleLength.op_Implicit(width); ((VisualElement)speedVE).style.width = StyleLength.op_Implicit(width); } } } private static void ApplyGapOverride() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (!usingFlexInsert) { return; } float value = Plugin.RowGapPx.Value; if (value != appliedGapPx) { appliedGapPx = value; if (float.IsNaN(value) || value < 0f) { oxygenAnchor.style.marginRight = StyleLength.op_Implicit((StyleKeyword)1); anchor.style.marginRight = StyleLength.op_Implicit((StyleKeyword)1); ((VisualElement)speedVE).style.marginRight = StyleLength.op_Implicit((StyleKeyword)1); } else { oxygenAnchor.style.marginRight = StyleLength.op_Implicit(value); anchor.style.marginRight = StyleLength.op_Implicit(value); ((VisualElement)speedVE).style.marginRight = StyleLength.op_Implicit(value); } } } private static void EnsureSampleCapacity() { int num = Mathf.Clamp(Plugin.RollingAverageTicks.Value, 1, 600); if (num != appliedCapacity) { appliedCapacity = num; sampleBuffer = new float[num]; sampleCount = 0; sampleIndex = 0; sampleSum = 0f; } } private static void PushSample(float speed) { if (sampleCount < sampleBuffer.Length) { sampleBuffer[sampleIndex] = speed; sampleSum += speed; sampleCount++; } else { sampleSum += speed - sampleBuffer[sampleIndex]; sampleBuffer[sampleIndex] = speed; } sampleIndex = (sampleIndex + 1) % sampleBuffer.Length; } private static void UpdateValue() { if (!(Time.unscaledTime < nextValueTime)) { nextValueTime = Time.unscaledTime + 0.1f; int num = Mathf.RoundToInt((Plugin.UseRollingAverage.Value && sampleCount > 0) ? (sampleSum / (float)sampleCount) : ReadSpeed()); if (num != lastShownSpeed) { lastShownSpeed = num; speedVE.SetValue(num, true); } } } private static float ReadSpeed() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) LocalPlayer i = LocalPlayer.I; if ((Object)(object)i == (Object)null) { return 0f; } Vector3 velocity = ((Player)i).Velocity; return ((Vector3)(ref velocity)).magnitude; } private static void Place() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (speedVE != null && anchor != null && !(Time.unscaledTime < nextPlaceTime)) { nextPlaceTime = Time.unscaledTime + 0.25f; Rect worldBound = anchor.worldBound; Rect worldBound2 = container.worldBound; if (!float.IsNaN(((Rect)(ref worldBound)).x) && !float.IsNaN(((Rect)(ref worldBound2)).x) && !(((Rect)(ref worldBound)).width <= 0f)) { float num = 6f * ((float)Screen.height / 1080f); ((VisualElement)speedVE).style.width = StyleLength.op_Implicit(((Rect)(ref worldBound)).width); ((VisualElement)speedVE).style.height = StyleLength.op_Implicit(((Rect)(ref worldBound)).height); ((VisualElement)speedVE).style.left = StyleLength.op_Implicit(((Rect)(ref worldBound)).xMin - ((Rect)(ref worldBound2)).xMin); ((VisualElement)speedVE).style.top = StyleLength.op_Implicit(((Rect)(ref worldBound)).yMin - ((Rect)(ref worldBound2)).yMin + (((Rect)(ref worldBound)).height + num)); } } } private static void Unwire() { //IL_0027: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (speedVE == null && (Object)(object)hud == (Object)null) { return; } if (oxygenAnchor != null) { oxygenAnchor.style.marginRight = StyleLength.op_Implicit((StyleKeyword)1); } if (anchor != null) { anchor.style.marginRight = StyleLength.op_Implicit((StyleKeyword)1); anchor.style.width = StyleLength.op_Implicit((StyleKeyword)1); } if (stackParent != null) { stackParent.style.alignSelf = StyleEnum.op_Implicit((StyleKeyword)1); } for (int i = 0; i < leftSpacers.Length; i++) { if (leftSpacers[i] != null) { leftSpacers[i].RemoveFromHierarchy(); } leftSpacers[i] = null; } if (speedVE != null) { ((VisualElement)speedVE).RemoveFromHierarchy(); } speedVE = null; hud = null; container = null; stackParent = null; anchor = null; oxygenAnchor = null; usingFlexInsert = false; appliedGapPx = float.NaN; appliedEqualWidth = float.NaN; lastShownSpeed = int.MinValue; appliedCapacity = -1; } internal static string DumpTree() { StringBuilder stringBuilder = new StringBuilder("Speedometer TREE DUMP\n"); if (container == null) { stringBuilder.AppendLine("not wired (HUD not found, or feature disabled). Enter a session and make sure the toggle is on, then retry."); return stringBuilder.ToString(); } stringBuilder.AppendLine("-- LocalPlayerContainer (depth-limited) --"); DumpChildren(stringBuilder, container, " ", 0, 5); stringBuilder.AppendLine("-- our element --"); Describe(stringBuilder, "speedVE", (VisualElement)(object)speedVE); if (speedVE != null) { DumpChildren(stringBuilder, (VisualElement)(object)speedVE, " ", 0, 3); } stringBuilder.AppendLine("-- ResourcePanel ancestor chain (root last) --"); DumpAncestors(stringBuilder, stackParent); return stringBuilder.ToString(); } private static void DumpAncestors(StringBuilder sb, VisualElement ve) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) int num = 0; while (ve != null && num < 15) { IResolvedStyle resolvedStyle = ve.resolvedStyle; sb.AppendLine(string.Format(" ^{0}: <{1}> name='{2}' classes=[{3}] flexDir={4} justifyContent={5} alignItems={6} alignSelf={7} flexGrow={8:F1} flexShrink={9:F1} flexBasis={10} pos={11} left={12:F1} width={13:F1} worldBound={14}", num, ((object)ve).GetType().Name, ve.name, string.Join(",", ve.GetClasses()), resolvedStyle.flexDirection, resolvedStyle.justifyContent, resolvedStyle.alignItems, resolvedStyle.alignSelf, resolvedStyle.flexGrow, resolvedStyle.flexShrink, resolvedStyle.flexBasis, resolvedStyle.position, resolvedStyle.left, resolvedStyle.width, ve.worldBound)); ve = ve.parent; num++; } } private static void Describe(StringBuilder sb, string label, VisualElement ve) { //IL_0082: 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_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) if (ve == null) { sb.AppendLine(label + ": "); return; } IResolvedStyle resolvedStyle = ve.resolvedStyle; Label val = (Label)(object)((ve is Label) ? ve : null); string text = ((val != null) ? (" text='" + ((TextElement)val).text + "'") : string.Empty); sb.AppendLine(string.Format("{0}: <{1}> name='{2}' classes=[{3}] display={4} vis={5} opacity={6:F2} pos={7} left={8:F1} top={9:F1} marginRight={10:F1} marginBottom={11:F1} size={12:F1}x{13:F1} worldBound={14}{15}", label, ((object)ve).GetType().Name, ve.name, string.Join(",", ve.GetClasses()), resolvedStyle.display, resolvedStyle.visibility, resolvedStyle.opacity, resolvedStyle.position, resolvedStyle.left, resolvedStyle.top, resolvedStyle.marginRight, resolvedStyle.marginBottom, resolvedStyle.width, resolvedStyle.height, ve.worldBound, text)); } private static void DumpChildren(StringBuilder sb, VisualElement ve, string indent, int depth, int maxDepth) { if (ve == null || depth > maxDepth) { return; } foreach (VisualElement item in ve.Children()) { Describe(sb, indent + "-", item); DumpChildren(sb, item, indent + " ", depth + 1, maxDepth); } } } }