Decompiled source of HelmetHUD v1.0.1
BepInEx/plugins/OstranautsHelmetHUD/OstranautsHelmetHUD.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using OstranautsHelmetHUD.Core; using OstranautsHelmetHUD.UI; using OstranautsSidebar.Data; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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 OstranautsSidebar.Data { internal static class CharacterDataReader { private const int DISPLAY_NEVER = 0; private static readonly HashSet<string> TraitNames = new HashSet<string>(StringComparer.Ordinal) { "IsAgliophobic", "IsAmbitious", "IsApathetic", "IsArrogant", "IsBeautiful", "IsBrave", "IsCharismatic", "IsChaste", "IsClumsy", "IsComedian", "IsComplacent", "IsCoordinated", "IsCraven", "IsCruel", "IsDiligent", "IsFeeble", "IsFinicky", "IsFit", "IsFollower", "IsForgiving", "IsFragile", "IsGenius", "IsGlutton", "IsGregarious", "IsHealerFast", "IsHealerSlow", "IsHonest", "IsHumble", "IsHumorless", "IsHypovolemic2", "IsImmunoSuppressed", "IsImpatient", "IsInsomniac", "IsIronStomach", "IsKind", "IsLeader", "IsLiar", "IsLoyal", "IsLustful", "IsMasochist", "IsMetabFast", "IsMetabSlow", "IsOblivious", "IsObservant", "IsObtuse", "IsOptimist", "IsPatient", "IsPessimist", "IsPoorHearing", "IsPushover", "IsQuitter", "IsRepulsive", "IsSelfish", "IsSelfless", "IsSharpHearing", "IsShy", "IsSleeperHeavy", "IsSleeperLight", "IsSlovenly", "IsSmoker", "IsStrong", "IsStubborn", "IsSuspicious", "IsTemperate", "IsTidy", "IsTough", "IsTreacherous", "IsTrusting", "IsUgly", "IsUnfit", "IsVengeful", "IsVisualImpaired", "IsWeakStomach" }; private static bool _loggedDiag; private static readonly HashSet<string> TraitTrainings = new HashSet<string>(StringComparer.Ordinal) { "Fit", "Strong", "Feeble", "Unfit" }; private static readonly Dictionary<string, (double val, double t)> _trainSeen = new Dictionary<string, (double, double)>(StringComparer.Ordinal); private const double TrainRecentSeconds = 600.0; private static HashSet<string>? _mttStatusNames; private const int DISPLAY_ALWAYS = 2; private static readonly Dictionary<string, bool> _needBackedCache = new Dictionary<string, bool>(StringComparer.Ordinal); private static readonly Dictionary<string, double> _prevValues = new Dictionary<string, double>(StringComparer.Ordinal); private static readonly Dictionary<string, TrendDirection> _lastTrend = new Dictionary<string, TrendDirection>(StringComparer.Ordinal); private static string _trendCharId = string.Empty; private const double TrendEpsilon = 0.25; private static readonly Dictionary<string, bool> _invertedTrend = new Dictionary<string, bool>(StringComparer.Ordinal); public static CharacterSnapshot? ReadPlayer() { CondOwner val; try { val = CrewSim.GetSelectedCrew(); CondOwner coPlayer = CrewSim.coPlayer; if ((Object)(object)val != (Object)null && (Object)(object)coPlayer != (Object)null && val != coPlayer) { bool flag; try { JsonCompany company = coPlayer.Company; flag = company != null && company.mapRoster?.ContainsKey(val.strID) == true; } catch (Exception) { flag = false; } if (!flag) { val = coPlayer; } } } catch (Exception ex2) { ModLog.Debug("ReadPlayer: CrewSim not ready (" + ex2.GetType().Name + ")."); return null; } if ((Object)(object)val == (Object)null || val.mapConds == null) { return null; } string text = val.strID ?? val.strName ?? string.Empty; bool flag2 = text != _trendCharId; if (flag2) { _trendCharId = text; _prevValues.Clear(); _lastTrend.Clear(); } EnsureMttStatusNames(); CharacterSnapshot characterSnapshot = new CharacterSnapshot { CharacterName = ((!string.IsNullOrEmpty(val.strNameFriendly)) ? val.strNameFriendly : (val.strName ?? "Unknown")) }; try { characterSnapshot.InChargen = val.HasCond("IsInChargen"); } catch (Exception) { characterSnapshot.InChargen = false; } foreach (Condition value in val.mapConds.Values) { if (value == null || string.IsNullOrEmpty(value.strName)) { continue; } if (value.strName.StartsWith("Skill", StringComparison.Ordinal)) { characterSnapshot.Skills.Add(new SkillEntry(CleanSkillName(value), learned: true, 0, DescOf(val, value))); } else if (value.strName.StartsWith("DcTraining", StringComparison.Ordinal)) { if (TryReadTraining(val, value, out SkillEntry entry, out bool isTrait)) { (isTrait ? characterSnapshot.TrainingTraits : characterSnapshot.TrainingSkills).Add(entry); } } else if (TraitNames.Contains(value.strName)) { characterSnapshot.Traits.Add(new TraitEntry(FriendlyName(value), PolarityOf(value), DescOf(val, value))); } else if (IsActiveStatus(value)) { characterSnapshot.ActiveConditions.Add(new ConditionEntry(FriendlyName(value), ToneOf(value), TrendOf(val, value), DescOf(val, value))); } } characterSnapshot.Skills.Sort((SkillEntry a, SkillEntry b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); characterSnapshot.TrainingSkills.Sort((SkillEntry a, SkillEntry b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); characterSnapshot.TrainingTraits.Sort((SkillEntry a, SkillEntry b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); characterSnapshot.Traits.Sort((TraitEntry a, TraitEntry b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); characterSnapshot.ActiveConditions.Sort(delegate(ConditionEntry a, ConditionEntry b) { int num = ToneRank(a.Tone).CompareTo(ToneRank(b.Tone)); return (num == 0) ? string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase) : num; }); if (flag2) { ModLog.Info("[diag] character switch → \"" + characterSnapshot.CharacterName + "\" (id=" + text + "): " + $"mapConds={val.mapConds.Count} skills={characterSnapshot.Skills.Count} " + $"trainSkills={characterSnapshot.TrainingSkills.Count} traits={characterSnapshot.Traits.Count} " + $"conditions={characterSnapshot.ActiveConditions.Count}"); } LogDiagnosticsOnce(val, characterSnapshot); return characterSnapshot; } private static void LogDiagnosticsOnce(CondOwner player, CharacterSnapshot s) { if (_loggedDiag) { return; } _loggedDiag = true; ModLog.Info($"[diag] mapConds={player.mapConds.Count} skills={s.Skills.Count} " + $"trainSkills={s.TrainingSkills.Count} trainTraits={s.TrainingTraits.Count} " + $"traits={s.Traits.Count} conditions={s.ActiveConditions.Count}"); List<string> list = new List<string>(); foreach (ConditionEntry activeCondition in s.ActiveConditions) { list.Add(activeCondition.Name + "[" + activeCondition.Tone.ToString() + "]"); } ModLog.Info(string.Format("[diag] active conditions ({0}): {1}", list.Count, string.Join(", ", list.ToArray()))); List<string> list2 = new List<string>(); foreach (Condition value in player.mapConds.Values) { if (value != null && value.strName != null && value.nDisplaySelf == 2 && !value.strName.StartsWith("Skill", StringComparison.Ordinal) && !value.strName.StartsWith("DcTraining", StringComparison.Ordinal) && !TraitNames.Contains(value.strName)) { list2.Add($"{value.strName}(other={value.nDisplayOther},{value.strColor})"); } } list2.Sort(); ModLog.Info(string.Format("[diag] disp2 pool ({0}): {1}", list2.Count, string.Join(", ", list2.ToArray()))); } private static bool TryReadTraining(CondOwner player, Condition cond, out SkillEntry entry, out bool isTrait) { entry = null; string text = cond.strName.Substring("DcTraining".Length).TrimEnd('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); isTrait = TraitTrainings.Contains(text); if (text.Length == 0) { return false; } string text2 = (isTrait ? ("Is" + text) : ("Skill" + text)); try { if (player.HasCond(text2)) { return false; } } catch (Exception) { } double num; try { num = player.GetCondAmount("StatTraining" + text); } catch (Exception) { num = 0.0; } int trainingPercent = (int)Math.Floor(Math.Max(0.0, Math.Min(100.0, num))); entry = new SkillEntry(CleanTrainingName(cond, text), learned: false, trainingPercent, DescOf(player, cond)); entry.RecentlyTrained = IsRecentlyTrained(text, num); return true; } private static bool IsRecentlyTrained(string suffix, double rawPct) { try { double fEpoch = StarSystem.fEpoch; string key = _trendCharId + "|" + suffix; if (_trainSeen.TryGetValue(key, out (double, double) value)) { if (Math.Abs(rawPct - value.Item1) > 0.0001) { value = (rawPct, fEpoch); _trainSeen[key] = value; } return fEpoch - value.Item2 <= 600.0; } _trainSeen[key] = (rawPct, double.NegativeInfinity); return false; } catch (Exception) { return true; } } private static string CleanTrainingName(Condition cond, string suffix) { string text = cond.strNameFriendly ?? string.Empty; if (text.StartsWith("Training: ", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("Training: ".Length); } int num = text.LastIndexOf(' '); if (num > 0 && text.EndsWith("%", StringComparison.Ordinal)) { text = text.Substring(0, num); } text = text.Trim(); if (text.Length <= 0) { return Prettify(suffix); } return text; } private static void EnsureMttStatusNames() { if (_mttStatusNames != null) { return; } try { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); string[] array = new string[2] { "CONDSocialGUIFilterDCs", "CONDSocialGUIFilterDCsMTT" }; for (int i = 0; i < array.Length; i++) { Loot loot = DataHandler.GetLoot(array[i]); List<string> list = ((loot != null) ? loot.GetLootNames((string)null, false, (string)null) : null); if (list == null || list.Count == 0) { return; } foreach (string item in list) { hashSet.Add(item); } } _mttStatusNames = hashSet; ModLog.Info($"MTT status filter loaded ({hashSet.Count} condition names)."); } catch (Exception ex) { ModLog.Debug("MTT status filter unavailable (" + ex.GetType().Name + "); using heuristic only."); } } private static bool IsActiveStatus(Condition cond) { if (cond.nDisplaySelf != 2) { return false; } if (_mttStatusNames != null && _mttStatusNames.Contains(cond.strName)) { return true; } if (cond.nDisplayOther < 1) { return false; } if (cond.strName.StartsWith("Is", StringComparison.Ordinal)) { return false; } if (cond.strName.StartsWith("Career", StringComparison.Ordinal)) { return false; } if (cond.strName.StartsWith("Dc", StringComparison.Ordinal) && !IsNeedBacked(cond)) { return false; } return true; } private static bool IsNeedBacked(Condition cond) { if (_needBackedCache.TryGetValue(cond.strName, out var value)) { return value; } bool flag; try { flag = (!string.IsNullOrEmpty(cond.strTrackCond) && DataHandler.GetCond(cond.strTrackCond) != null) || DataHandler.GetCond("Stat" + cond.strName.Substring(2).TrimEnd('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) != null; } catch (Exception) { return true; } _needBackedCache[cond.strName] = flag; return flag; } private static TraitPolarity PolarityOf(Condition cond) { if (string.Equals(cond.strColor, "Good", StringComparison.OrdinalIgnoreCase)) { return TraitPolarity.Positive; } if (cond.bNegative || string.Equals(cond.strColor, "Bad", StringComparison.OrdinalIgnoreCase)) { return TraitPolarity.Negative; } return TraitPolarity.Neutral; } private static int ToneRank(ConditionTone tone) { return tone switch { ConditionTone.Critical => 0, ConditionTone.Warning => 1, ConditionTone.Neutral => 2, _ => 3, }; } private static ConditionTone ToneOf(Condition cond) { string text = cond.strColor ?? string.Empty; if (text.StartsWith("Good", StringComparison.OrdinalIgnoreCase)) { return ConditionTone.Good; } if (cond.bFatal || cond.bKO || cond.bAlert || string.Equals(text, "BadVery", StringComparison.OrdinalIgnoreCase)) { return ConditionTone.Critical; } if (cond.bNegative || text.StartsWith("Bad", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Cold", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "Warning", StringComparison.OrdinalIgnoreCase)) { return ConditionTone.Warning; } return ConditionTone.Neutral; } private static bool IsInvertedTrend(string trackKey) { if (string.IsNullOrEmpty(trackKey)) { return false; } if (_invertedTrend.TryGetValue(trackKey, out var value)) { return value; } bool flag = false; try { if (DataHandler.dictConds != null) { foreach (JsonCond value2 in DataHandler.dictConds.Values) { if (value2.strTrackCond == trackKey && string.Equals(value2.strColor, "Good", StringComparison.OrdinalIgnoreCase)) { flag = value2.bInvert; break; } } } } catch (Exception) { flag = false; } _invertedTrend[trackKey] = flag; return flag; } private static TrendDirection TrendOf(CondOwner player, Condition cond) { string text = ((!string.IsNullOrEmpty(cond.strTrackCond)) ? cond.strTrackCond : cond.strName); double num; try { num = ((!string.IsNullOrEmpty(cond.strTrackCond)) ? player.GetCondAmount(cond.strTrackCond) : cond.fCount); } catch (Exception) { num = cond.fCount; } if (!_prevValues.TryGetValue(text, out var value)) { _prevValues[text] = num; _lastTrend[text] = TrendDirection.None; return TrendDirection.None; } double num2 = num - value; bool flag = IsInvertedTrend(text); if (num2 >= 0.25) { _lastTrend[text] = ((!flag) ? TrendDirection.Up : TrendDirection.Down); _prevValues[text] = num; } else if (num2 <= -0.25) { _lastTrend[text] = (flag ? TrendDirection.Up : TrendDirection.Down); _prevValues[text] = num; } if (!_lastTrend.TryGetValue(text, out var value2)) { return TrendDirection.None; } return value2; } private static string FriendlyName(Condition cond) { if (string.IsNullOrEmpty(cond.strNameFriendly)) { return cond.strName; } return cond.strNameFriendly; } private static string DescOf(CondOwner player, Condition cond) { if (string.IsNullOrEmpty(cond.strDesc)) { return string.Empty; } try { return GrammarUtils.GetInflectedString(cond.strDesc, (object)player); } catch (Exception) { return cond.strDesc; } } private static string CleanSkillName(Condition cond) { string text = FriendlyName(cond); if (text.StartsWith("Skilled in ", StringComparison.OrdinalIgnoreCase)) { return text.Substring("Skilled in ".Length); } if (!string.IsNullOrEmpty(cond.strNameFriendly)) { return text; } return Prettify(cond.strName.Substring("Skill".Length)); } private static string Prettify(string raw) { if (string.IsNullOrEmpty(raw)) { return raw; } StringBuilder stringBuilder = new StringBuilder(raw.Length + 4); for (int i = 0; i < raw.Length; i++) { char c = raw[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(raw[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } } public class CharacterSnapshot { public string CharacterName { get; set; } = string.Empty; public bool InChargen { get; set; } public List<SkillEntry> Skills { get; } = new List<SkillEntry>(); public List<SkillEntry> TrainingSkills { get; } = new List<SkillEntry>(); public List<SkillEntry> TrainingTraits { get; } = new List<SkillEntry>(); public List<TraitEntry> Traits { get; } = new List<TraitEntry>(); public List<ConditionEntry> ActiveConditions { get; } = new List<ConditionEntry>(); } public class SkillEntry { public string Name { get; } public bool Learned { get; } public int TrainingPercent { get; } public bool RecentlyTrained { get; set; } public string Description { get; } public SkillEntry(string name, bool learned, int trainingPercent = 0, string description = "") { Name = name; Learned = learned; TrainingPercent = trainingPercent; Description = description; } } public class TraitEntry { public string Name { get; } public TraitPolarity Polarity { get; } public string Description { get; } public TraitEntry(string name, TraitPolarity polarity, string description = "") { Name = name; Polarity = polarity; Description = description; } } public enum TraitPolarity { Positive, Negative, Neutral } public class ConditionEntry { public string Name { get; } public ConditionTone Tone { get; } public TrendDirection Trend { get; } public string Description { get; } public ConditionEntry(string name, ConditionTone tone, TrendDirection trend, string description = "") { Name = name; Tone = tone; Trend = trend; Description = description; } } public enum ConditionTone { Good, Neutral, Warning, Critical } public enum TrendDirection { None, Up, Down } internal static class ModLog { internal static ManualLogSource? Log; internal static void Info(string msg) { ManualLogSource? log = Log; if (log != null) { log.LogInfo((object)msg); } } internal static void Debug(string msg) { ManualLogSource? log = Log; if (log != null) { log.LogDebug((object)msg); } } internal static void Warn(string msg) { ManualLogSource? log = Log; if (log != null) { log.LogWarning((object)msg); } } } } namespace OstranautsHelmetHUD { public static class HelmetLink { public const int Version = 1; public static bool HelmetOn { get; internal set; } public static bool ReadoutActive { get; internal set; } } [BepInPlugin("com.ostranauts.helmethud", "Helmet HUD", "1.0.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static Plugin Instance; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> OnPressureSuits; internal static ConfigEntry<bool> StockOldHelmets; internal static ConfigEntry<int> CellsRequired; internal static ConfigEntry<float> CellHours; internal static ConfigEntry<float> GlowRadius; internal static ConfigEntry<bool> BlinkCritical; internal static ConfigEntry<bool> RequireBatteries; internal static ConfigEntry<float> ReferenceHeight; internal static ConfigEntry<float> RectX; internal static ConfigEntry<float> RectY; internal static ConfigEntry<float> RectW; internal static ConfigEntry<float> RectH; internal static ConfigEntry<float> EdgeInset; internal static ConfigEntry<float> SocketX; internal static ConfigEntry<float> SocketTop; internal static ConfigEntry<float> SocketPitch; internal static ConfigEntry<float> SocketSize; internal static ConfigEntry<int> MaxRowsEVA; internal static ConfigEntry<int> MaxRowsPS; internal static ConfigEntry<float> PageSeconds; private void Awake() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Expected O, but got Unknown //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Expected O, but got Unknown //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Expected O, but got Unknown //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Expected O, but got Unknown //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Expected O, but got Unknown //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Expected O, but got Unknown //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Expected O, but got Unknown //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Expected O, but got Unknown //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_03f6: Expected O, but got Unknown //IL_041f: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Expected O, but got Unknown //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Expected O, but got Unknown //IL_0484: Unknown result type (might be due to invalid IL or missing references) //IL_0489: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModLog.Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Behaviour", "Enabled", true, "Show the suit readout inside the visor while wearing a powered helmet."); OnPressureSuits = ((BaseUnityPlugin)this).Config.Bind<bool>("Behaviour", "OnPressureSuits", true, "Also show it on pressure suits. Off = EVA suits with a running life-support pack only."); RequireBatteries = ((BaseUnityPlugin)this).Config.Bind<bool>("Behaviour", "RequireBatteries", true, "A pressure suit helmet needs two charged BB cells to run its gauges — flat cells mean a dark visor, including the vanilla O2 and pressure lamps. This is a real survival mechanic: without power you lose those warnings. EVA suits are unaffected; they run off their life-support pack as usual."); CellsRequired = ((BaseUnityPlugin)this).Config.Bind<int>("Behaviour", "CellsRequired", 2, new ConfigDescription("How many charged BB cells the pressure suit helmet's gauges need. The helmet ships with two, so raising this to 3 is also the only way to see the half-fitted warning state on a helmet that's already stocked — the game gives you no way to split a cell stack by hand.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 4), Array.Empty<object>())); CellHours = ((BaseUnityPlugin)this).Config.Bind<float>("Behaviour", "CellHours", 12f, new ConfigDescription("How many hours of continuous wear a full set of gauge cells lasts. Only the helmet you are actually wearing runs down, and only in game time, so fast-forwarding costs charge at the same rate as living through it. Set 0 to switch the drain off and go back to cells that never deplete. For reference, 44.4 matches the draw of a work lamp - which is also the draw of every ceiling light and alarm in the game, they all share one rate - and is long enough that you would rarely notice the cells at all. The default is roughly a shift and a half: long enough to ignore on a short EVA, short enough that a long one wants a spare pair.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>())); GlowRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Behaviour", "VisorGlowRadius", 3f, new ConfigDescription("How far the lit visor throws light onto the wearer, the way a PDA glows. Follows the readout, so it dies when the cells do. Phosphor green on a pressure suit, cooler and bluer on an EVA suit to match its gas bars. Set 0 to switch the glow off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 12f), Array.Empty<object>())); StockOldHelmets = ((BaseUnityPlugin)this).Config.Bind<bool>("Behaviour", "StockOldHelmets", true, "Fit a pair of BB cells to a worn pressure suit helmet that predates this mod's battery container. Happens once per helmet — removing the cells later won't produce free replacements. Turn off if you'd rather source your own."); BlinkCritical = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "BlinkCritical", true, "Pulse critical conditions (hypoxia, hypercapnia, severe blood loss and the like) to draw the eye. Turn off if you'd rather nothing on screen flashes."); ReferenceHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "ReferenceHeight", 720f, new ConfigDescription("Canvas reference height. Lower = larger text. Applies on next game load.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(480f, 1440f), Array.Empty<object>())); RectX = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "RectX", 0.81f, new ConfigDescription("Left edge of the readout, as a fraction of the helmet frame width.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); RectY = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "RectY", 0.321f, new ConfigDescription("Bottom edge of the readout, as a fraction of the helmet frame height.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); RectW = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "RectW", 0.135f, new ConfigDescription("Readout width, as a fraction of the helmet frame width.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.5f), Array.Empty<object>())); RectH = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "RectH", 0.5f, new ConfigDescription("Readout height, as a fraction of the helmet frame height.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 0.9f), Array.Empty<object>())); EdgeInset = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "EdgeInset", 0.003f, new ConfigDescription("How far inside the visor rim the right-justified text sits, in frame widths. The rim itself is a quadratic fitted to the helmet art; this just offsets from it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-0.05f, 0.1f), Array.Empty<object>())); SocketX = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "GaugeSocketX", 0.1938f, new ConfigDescription("Lamp column centre, as a fraction of the gauge panel's width.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); SocketTop = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "GaugeSocketTop", 0.2165f, new ConfigDescription("Centre of the first (topmost) lamp socket, as a fraction of the panel's height measured from its top edge.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); SocketPitch = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "GaugeSocketPitch", 0.0777f, new ConfigDescription("Spacing between lamp sockets, as a fraction of the panel's height.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.5f), Array.Empty<object>())); SocketSize = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "GaugeSocketSize", 0.0278f, new ConfigDescription("Lamp diameter, as a fraction of the panel's height.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.005f, 0.2f), Array.Empty<object>())); MaxRowsEVA = ((BaseUnityPlugin)this).Config.Bind<int>("Paging", "MaxRowsEVA", 6, new ConfigDescription("Visible lines (including the section header) on an EVA suit, whose life-support bars fill most of the right rim.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 24), Array.Empty<object>())); MaxRowsPS = ((BaseUnityPlugin)this).Config.Bind<int>("Paging", "MaxRowsPressureSuit", 18, new ConfigDescription("Same for pressure suits, which only show two small gauge lamps low on the rim and leave far more room.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 24), Array.Empty<object>())); PageSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Paging", "PageSeconds", 4f, new ConfigDescription("How long each page is shown before cycling, when there is more than one.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1.5f, 15f), Array.Empty<object>())); Log.LogInfo((object)"Helmet HUD v1.0.0 loading..."); DataPackInstaller.Run(); GameObject val = new GameObject("OstranautsHelmetHUDController"); val.AddComponent<HelmetController>(); Object.DontDestroyOnLoad((Object)val); } private void OnDestroy() { HelmetController.Instance?.Teardown(); } } internal static class PluginInfo { public const string GUID = "com.ostranauts.helmethud"; public const string NAME = "Helmet HUD"; public const string VERSION = "1.0.0"; } } namespace OstranautsHelmetHUD.UI { internal enum CellState { Powered, Low, Unpowered } internal sealed class GaugeLamps { private sealed class Bulb { public Image? Img; public Sprite? On; public Sprite? Off; public Color Tint = Color.white; public Color Ink = Color.white; public bool HasFrames { get { if ((Object)(object)On != (Object)null && (Object)(object)Off != (Object)null) { return (Object)(object)On != (Object)(object)Off; } return false; } } } private struct RectState { public Vector2 AMin; public Vector2 AMax; public Vector2 Pivot; public Vector2 Pos; public Vector2 Size; public Vector3 Scale; public static RectState Of(RectTransform rt) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) return new RectState { AMin = rt.anchorMin, AMax = rt.anchorMax, Pivot = rt.pivot, Pos = rt.anchoredPosition, Size = rt.sizeDelta, Scale = ((Transform)rt).localScale }; } public void ApplyTo(RectTransform rt) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) rt.anchorMin = AMin; rt.anchorMax = AMax; rt.pivot = Pivot; rt.anchoredPosition = Pos; rt.sizeDelta = Size; ((Transform)rt).localScale = Scale; } } internal const string NamePrefix = "HelmetHUD_Lamp"; private const int RowO2 = 0; private const int RowCO2 = 1; private const int RowPower = 2; private const int RowBatt = 3; private const int Rows = 4; private readonly RectTransform?[] _rows = (RectTransform?[])(object)new RectTransform[4]; private readonly RectTransform? _panel; private readonly Bulb _power = new Bulb(); private readonly Bulb _batt = new Bulb(); private bool _placed; private const float BlinkPeriod = 1f; private const float BlinkOnFraction = 0.55f; private static readonly Dictionary<int, RectState> _home = new Dictionary<int, RectState>(); internal GaugeLamps(Transform gauge, RectTransform upper, RectTransform lower) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) _panel = (RectTransform?)(object)((gauge is RectTransform) ? gauge : null); _rows[0] = upper; _rows[1] = lower; for (int num = gauge.childCount - 1; num >= 0; num--) { Transform child = gauge.GetChild(num); if (((Object)child).name.StartsWith("HelmetHUD_Lamp", StringComparison.Ordinal)) { Object.DestroyImmediate((Object)(object)((Component)child).gameObject); } } Sprite val = null; Sprite val2 = null; try { GUILamp component = ((Component)lower).GetComponent<GUILamp>(); Sprite[] array = (((Object)(object)component != (Object)null) ? component.aSprites : null); if (array != null && array.Length != 0) { val = array[0]; val2 = array[^1]; } } catch (Exception) { } _power.Off = LoadModSprite("helmethud_lamp_power_off.png") ?? LoadModSprite("helmethud_lamp_off.png") ?? val; _power.On = LoadModSprite("helmethud_lamp_power_on.png") ?? LoadModSprite("helmethud_lamp_on.png") ?? val2; _power.Tint = new Color(0.36f, 0.89f, 0.27f, 1f); _batt.Off = LoadModSprite("helmethud_lamp_batt_off.png") ?? LoadModSprite("helmethud_lamp_off.png") ?? val; _batt.On = LoadModSprite("helmethud_lamp_batt_on.png") ?? LoadModSprite("helmethud_lamp_on.png") ?? val2; _batt.Tint = new Color(1f, 0.69f, 0.17f, 1f); _rows[2] = Clone(gauge, lower, "HelmetHUD_LampPower", _power); _rows[3] = Clone(gauge, lower, "HelmetHUD_LampBatt", _batt); Plugin.Log.LogInfo((object)("[diag] gauge lamps: 4-socket column built (added POWER + CHNG BATT; power art: " + Describe(_power) + ", batt art: " + Describe(_batt) + ").")); } private static string Describe(Bulb b) { if (!b.HasFrames) { return "tint fallback (no distinct lit frame)"; } return "lit/unlit frames"; } private RectTransform? Clone(Transform parent, RectTransform template, string name, Bulb bulb) { //IL_008b: 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) try { GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, parent, false); ((Object)val).name = name; GUILamp component = val.GetComponent<GUILamp>(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } for (int num = val.transform.childCount - 1; num >= 0; num--) { Object.DestroyImmediate((Object)(object)((Component)val.transform.GetChild(num)).gameObject); } bulb.Img = val.GetComponent<Image>(); if ((Object)(object)bulb.Img != (Object)null) { ((Graphic)bulb.Img).raycastTarget = false; bulb.Ink = ((Graphic)bulb.Img).color; if ((Object)(object)bulb.Off != (Object)null) { bulb.Img.sprite = bulb.Off; } } SetLayerRecursive(val, ((Component)parent).gameObject.layer); val.SetActive(true); return val.GetComponent<RectTransform>(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't build the " + name + " lamp (" + ex.GetType().Name + ": " + ex.Message + "). The other gauge lamps are unaffected.")); return null; } } private static Sprite? LoadModSprite(string file) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) try { Texture2D val = DataHandler.LoadPNG(file, false, false); if ((Object)(object)val == (Object)null || ((Object)val).name == "missing.png") { return null; } ((Texture)val).filterMode = (FilterMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } catch (Exception) { return null; } } private static void SetLayerRecursive(GameObject go, int layer) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) go.layer = layer; foreach (Transform item in go.transform) { SetLayerRecursive(((Component)item).gameObject, layer); } } internal void SetVisible(bool on) { SetActive(_power.Img, on); SetActive(_batt.Img, on); if (!on && _placed) { RestoreRects(); _placed = false; } } private static void SetActive(Image? img, bool on) { if ((Object)(object)img != (Object)null && ((Component)img).gameObject.activeSelf != on) { ((Component)img).gameObject.SetActive(on); } } internal void Apply(CellState state, float charge) { try { Place(); bool flag = !Plugin.BlinkCritical.Value || Time.unscaledTime % 1f < 0.55f; bool lit = state != CellState.Unpowered; bool lit2 = state switch { CellState.Low => flag, CellState.Unpowered => charge > 0f, _ => false, }; SetBulb(_power, lit); SetBulb(_batt, lit2); } catch (Exception) { } } private static void SetBulb(Bulb bulb, bool lit) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) Image img = bulb.Img; if ((Object)(object)img == (Object)null) { return; } if (bulb.HasFrames) { Sprite val = (lit ? bulb.On : bulb.Off); if ((Object)(object)img.sprite != (Object)(object)val) { img.sprite = val; } } else { Color val2 = (lit ? bulb.Tint : bulb.Ink); if (((Graphic)img).color != val2) { ((Graphic)img).color = val2; } } } private void Place() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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_00d7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_panel == (Object)null) { return; } float value = Plugin.SocketX.Value; float value2 = Plugin.SocketTop.Value; float value3 = Plugin.SocketPitch.Value; float value4 = Plugin.SocketSize.Value; Rect rect = _panel.rect; float num = Mathf.Max(1f, value4 * ((Rect)(ref rect)).height); Vector2 val2 = default(Vector2); for (int i = 0; i < 4; i++) { RectTransform val = _rows[i]; if (!((Object)(object)val == (Object)null)) { Home(val); ((Vector2)(ref val2))..ctor(value, 1f - (value2 + value3 * (float)i)); val.anchorMin = val2; val.anchorMax = val2; val.pivot = new Vector2(0.5f, 0.5f); val.anchoredPosition = Vector2.zero; val.sizeDelta = new Vector2(num, num); ((Transform)val).localScale = Vector3.one; } } _placed = true; } private static void Home(RectTransform rt) { int instanceID = ((Object)rt).GetInstanceID(); if (!_home.ContainsKey(instanceID)) { _home[instanceID] = RectState.Of(rt); } } private void RestoreRects() { for (int i = 0; i < 4; i++) { RectTransform val = _rows[i]; if (!((Object)(object)val == (Object)null) && _home.TryGetValue(((Object)val).GetInstanceID(), out var value)) { value.ApplyTo(val); } } } internal void Dispose() { try { RestoreRects(); _placed = false; if ((Object)(object)_power.Img != (Object)null) { Object.Destroy((Object)(object)((Component)_power.Img).gameObject); } if ((Object)(object)_batt.Img != (Object)null) { Object.Destroy((Object)(object)((Component)_batt.Img).gameObject); } _power.Img = null; _batt.Img = null; } catch (Exception) { } } } internal sealed class GaugeSkin { internal const string ArtFile = "helmethud_gauge_panel.png"; private readonly Image? _img; private readonly RawImage? _raw; private readonly Sprite? _origSprite; private readonly Texture? _origTex; private Sprite? _made; internal bool Applied { get; private set; } internal GaugeSkin(Transform gauge) { _img = ((Component)gauge).GetComponent<Image>(); _raw = (((Object)(object)_img == (Object)null) ? ((Component)gauge).GetComponent<RawImage>() : null); if ((Object)(object)_img == (Object)null && (Object)(object)_raw == (Object)null) { Plugin.Log.LogInfo((object)"[diag] gauge skin: no Image/RawImage on bmpGauge, nothing to re-skin."); return; } Texture2D val = LoadArt(); if ((Object)(object)val == (Object)null) { Plugin.Log.LogInfo((object)"[diag] gauge skin: no helmethud_gauge_panel.png in the data pack — keeping the game's panel art."); return; } try { if ((Object)(object)_img != (Object)null) { _origSprite = _img.sprite; _made = Rebuild(val, _origSprite); _img.sprite = _made; } else { _origTex = _raw.texture; _raw.texture = (Texture)(object)val; } Applied = true; Plugin.Log.LogInfo((object)string.Format("[diag] gauge skin: panel art replaced from {0} ({1}x{2}).", "helmethud_gauge_panel.png", ((Texture)val).width, ((Texture)val).height)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't apply the custom gauge panel art (" + ex.GetType().Name + ": " + ex.Message + "). The game's own panel is used instead; nothing else is affected.")); } } private static Texture2D? LoadArt() { try { Texture2D val = DataHandler.LoadPNG("helmethud_gauge_panel.png", false, false); return ((Object)(object)val == (Object)null || ((Object)val).name == "missing.png") ? null : val; } catch (Exception) { return null; } } private static Sprite Rebuild(Texture2D tex, Sprite? orig) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_007b: 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_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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) Rect val = default(Rect); ((Rect)(ref val))..ctor(0f, 0f, (float)((Texture)tex).width, (float)((Texture)tex).height); if ((Object)(object)orig == (Object)null) { return Sprite.Create(tex, val, new Vector2(0.5f, 0.5f), 100f); } Rect rect = orig.rect; float num = Mathf.Max(1f, ((Rect)(ref rect)).width); rect = orig.rect; float num2 = Mathf.Max(1f, ((Rect)(ref rect)).height); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(orig.pivot.x / num, orig.pivot.y / num2); float num3 = (float)((Texture)tex).width / num; float num4 = (float)((Texture)tex).height / num2; Vector4 border = orig.border; Vector4 val3 = default(Vector4); ((Vector4)(ref val3))..ctor(border.x * num3, border.y * num4, border.z * num3, border.w * num4); return Sprite.Create(tex, val, val2, orig.pixelsPerUnit, 0u, (SpriteMeshType)1, val3); } internal void Restore() { try { if (Applied) { if ((Object)(object)_img != (Object)null) { _img.sprite = _origSprite; } else if ((Object)(object)_raw != (Object)null) { _raw.texture = _origTex; } if ((Object)(object)_made != (Object)null) { Object.Destroy((Object)(object)_made); } _made = null; Applied = false; } } catch (Exception) { } } } public class VisorReadout : MonoBehaviour { private struct Row { public GameObject GO; public RectTransform txt; public Text label; public float height; public bool blink; } private sealed class Section { public string Title = ""; public readonly List<Row> Rows = new List<Row>(); } private static readonly Color Phos = new Color(0.66f, 0.91f, 0.3f, 1f); private static readonly Color PhosDim = Phos; private static readonly Color PhosBright = Phos; private static readonly Color PhosCritical = Phos; private static readonly Color PhosHeader = Phos; private static readonly Color ShadowColor = new Color(0f, 0f, 0f, 0.6f); private RectTransform _rt; private Canvas _ourCanvas; private Transform _content; private RectTransform? _tfHelmet; private CanvasGroup? _cgFrame; private CanvasGroup? _cgHud; private CanvasGroup? _cgGauge; private Canvas? _helmetCanvas; private float _lastAcquireTry; private static readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4]; private string _sig = ""; private const float BlinkPeriod = 1f; private const float BlinkOnFraction = 0.6f; private const float BlinkDimAlpha = 0.22f; private readonly List<Section> _sections = new List<Section>(); private Row _headerRow; private int _sectionIndex; private int _pageStart; private float _lastPageFlip; private bool _evaTier; private Text? _headerText; private const float RowSpacing = 1f; private float _lastRectH; private const float EdgeA = 0.89827f; private const float EdgeB = 0.22348f; private const float EdgeC = -0.29296f; private const float EdgeDomainMin = 0.18f; private const float EdgeDomainMax = 0.55f; private const int MaxConditionRows = 14; private const int MaxTrainingRows = 6; private GameObject _readoutGo; private string _lastGates = ""; private bool _loggedPlacement; private static int _uiLayer = 5; private bool _psHelmetWorn; private bool _psHasPower; private float _psCharge; private GaugeLamps? _powerLamp; private Transform? _gaugeRoot; private RectTransform? _lampUpper; private RectTransform? _lampLower; private GaugeSkin? _gaugeSkin; private Graphic[]? _vanillaLamps; private Color[]? _vanillaLampHome; private bool _lampsDarkened; private static bool _warnedNoGauge; private static readonly Color UnlitLamp = new Color(0.22f, 0.21f, 0.18f, 0.95f); private const float LowChargeFraction = 0.1f; private float _frameX; private float _frameY; private float _frameW; private float _frameH; public bool EvaTier => _evaTier; public bool HelmetVisible { get { if ((Object)(object)_cgFrame != (Object)null) { return _cgFrame.alpha > 0.5f; } return false; } } private static float RimXFrac(float yFracFromTop) { float num = Mathf.Clamp(yFracFromTop, 0.18f, 0.55f); return 0.89827f + 0.22348f * num + -0.29296f * num * num; } public static VisorReadout Create() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Expected O, but got Unknown //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("OstranautsVisorCanvas"); Canvas val2 = val.AddComponent<Canvas>(); Canvas val3 = null; try { val3 = (((Object)(object)CanvasManager.instance != (Object)null && (Object)(object)CanvasManager.instance.goCanvasCrewBar != (Object)null) ? CanvasManager.instance.goCanvasCrewBar.GetComponent<Canvas>() : null); } catch (Exception) { } if ((Object)(object)val3 != (Object)null && (Object)(object)val3.worldCamera != (Object)null) { val2.renderMode = (RenderMode)1; val2.worldCamera = val3.worldCamera; val2.planeDistance = val3.planeDistance; val2.sortingOrder = val3.sortingOrder - 1; _uiLayer = ((Component)val3).gameObject.layer; val.layer = _uiLayer; Plugin.Log.LogInfo((object)$"[diag] visor canvas: camera-space under the MTT (order {val2.sortingOrder}, layer {_uiLayer} '{LayerMask.LayerToName(_uiLayer)}')."); } else { val2.renderMode = (RenderMode)0; val2.sortingOrder = 5000; Plugin.Log.LogInfo((object)"[diag] visor canvas: overlay fallback (crew bar canvas not found) — will draw over the MTT."); } CanvasScaler obj = val.AddComponent<CanvasScaler>(); obj.uiScaleMode = (ScaleMode)1; obj.referenceResolution = new Vector2(1920f, Plugin.ReferenceHeight.Value); obj.screenMatchMode = (ScreenMatchMode)0; obj.matchWidthOrHeight = 1f; VisorReadout visorReadout = val.AddComponent<VisorReadout>(); visorReadout._ourCanvas = val2; GameObject val4 = new GameObject("Readout"); val4.layer = _uiLayer; val4.transform.SetParent(val.transform, false); visorReadout._readoutGo = val4; visorReadout._rt = val4.AddComponent<RectTransform>(); visorReadout._rt.anchorMin = Vector2.zero; visorReadout._rt.anchorMax = Vector2.zero; visorReadout._rt.pivot = Vector2.zero; GameObject val5 = new GameObject("Content"); val5.layer = _uiLayer; val5.transform.SetParent(val4.transform, false); RectTransform obj2 = val5.AddComponent<RectTransform>(); obj2.anchorMin = Vector2.zero; obj2.anchorMax = Vector2.one; obj2.offsetMin = new Vector2(6f, 4f); obj2.offsetMax = new Vector2(-6f, -4f); VerticalLayoutGroup obj3 = val5.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj3).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj3).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandHeight = false; ((LayoutGroup)obj3).childAlignment = (TextAnchor)0; ((HorizontalOrVerticalLayoutGroup)obj3).spacing = 1f; visorReadout._content = val5.transform; val4.SetActive(false); return visorReadout; } public bool UpdateAnchor() { //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_0370: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Unknown result type (might be due to invalid IL or missing references) if (!EnsureRefs()) { return false; } if ((Object)(object)_cgFrame != (Object)null && _cgFrame.alpha > 0.05f) { string text = string.Format("frame={0:F2} hud={1} ", _cgFrame.alpha, ((Object)(object)_cgHud != (Object)null) ? _cgHud.alpha.ToString("F2") : "null") + "gauge=" + (((Object)(object)_cgGauge != (Object)null) ? _cgGauge.alpha.ToString("F2") : "null") + " " + $"cfgEnabled={Plugin.Enabled.Value} cfgPS={Plugin.OnPressureSuits.Value} sections={_sections.Count} active={_readoutGo.activeSelf} layer={_uiLayer}"; if (text != _lastGates) { _lastGates = text; Plugin.Log.LogInfo((object)("[diag] visor gates: " + text)); } } if ((Object)(object)_cgFrame == (Object)null || _cgFrame.alpha <= 0.5f) { return false; } if (!MeasureFrame()) { return false; } bool flag = (Object)(object)_cgHud != (Object)null && _cgHud.alpha > 0.5f; bool psHelmetWorn = _psHelmetWorn; bool flag2 = Plugin.OnPressureSuits.Value && psHelmetWorn && _psHasPower; SetVanillaLampsDark(psHelmetWorn && Plugin.RequireBatteries.Value && !_psHasPower); PlacePowerLamp(psHelmetWorn); if (!flag && !flag2) { return false; } _evaTier = flag; try { float frameX = _frameX; float frameY = _frameY; float frameW = _frameW; float frameH = _frameH; float num = _ourCanvas.scaleFactor; if (num <= 0f) { num = 1f; } _rt.anchoredPosition = new Vector2(frameX + frameW * Plugin.RectX.Value, frameY + frameH * Plugin.RectY.Value); float num2 = frameW * Plugin.RectW.Value; float num3 = frameH * Plugin.RectH.Value; _rt.sizeDelta = new Vector2(num2, num3); if (!_loggedPlacement) { _loggedPlacement = true; Vector2 anchoredPosition = _rt.anchoredPosition; Plugin.Log.LogInfo((object)($"[diag] visor placement: pos=({anchoredPosition.x:F0},{anchoredPosition.y:F0}) size=({num2:F0}x{num3:F0}) canvasUnits; " + $"frame=({frameX:F0},{frameY:F0}) {frameW:F0}x{frameH:F0} screen={Screen.width}x{Screen.height} sf={num:F2}; " + $"→ screenRect x {anchoredPosition.x * num:F0}-{(anchoredPosition.x + num2) * num:F0}, y {anchoredPosition.y * num:F0}-{(anchoredPosition.y + num3) * num:F0}")); } if (Mathf.Abs(num3 - _lastRectH) > 1f) { _sig = ""; } _lastRectH = num3; ApplyLayout(frameX, frameW, frameH, (float)Screen.width / num); return true; } catch (Exception) { DropRefs(); return false; } } private void DropRefs() { _tfHelmet = null; _loggedPlacement = false; } private void SetVanillaLampsDark(bool dark) { //IL_004a: 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) if (_vanillaLamps == null || _vanillaLampHome == null || dark == _lampsDarkened) { return; } _lampsDarkened = dark; for (int i = 0; i < _vanillaLamps.Length; i++) { Graphic val = _vanillaLamps[i]; if (!((Object)(object)val == (Object)null)) { val.color = (dark ? UnlitLamp : _vanillaLampHome[i]); } } } private void PlacePowerLamp(bool wearingPS) { bool flag = wearingPS && Plugin.RequireBatteries.Value; if (_powerLamp == null) { if (!flag) { return; } if ((Object)(object)_gaugeRoot == (Object)null || (Object)(object)_lampUpper == (Object)null || (Object)(object)_lampLower == (Object)null) { if (!_warnedNoGauge) { _warnedNoGauge = true; Plugin.Log.LogWarning((object)"Couldn't find the pressure suit helmet's gauge cluster (bmpHelmet/bmpGauge and its two lamps), so the cells indicator has nowhere to sit. The battery mechanic still works and the readout is unaffected — you just won't get the lamp. This usually means another mod has replaced the helmet UI."); } return; } _powerLamp = new GaugeLamps(_gaugeRoot, _lampUpper, _lampLower); } _powerLamp.SetVisible(flag); if (flag) { _powerLamp.Apply(CellsState(), _psCharge); } } private CellState CellsState() { if (!_psHasPower) { return CellState.Unpowered; } if (_psCharge < 0.1f) { return CellState.Low; } return CellState.Powered; } public void Suspend() { SetVisible(visible: false); SetVanillaLampsDark(dark: false); _powerLamp?.SetVisible(on: false); } private void OnDestroy() { SetVanillaLampsDark(dark: false); _powerLamp?.Dispose(); _powerLamp = null; _gaugeSkin?.Restore(); _gaugeSkin = null; } private bool MeasureFrame() { //IL_0014: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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) //IL_0075: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: 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) try { Camera obj = (((Object)(object)_helmetCanvas != (Object)null && (int)_helmetCanvas.renderMode != 0) ? _helmetCanvas.worldCamera : null); _tfHelmet.GetWorldCorners(_corners); Vector2 val = RectTransformUtility.WorldToScreenPoint(obj, _corners[0]); Vector2 val2 = RectTransformUtility.WorldToScreenPoint(obj, _corners[2]); float num = _ourCanvas.scaleFactor; if (num <= 0f) { num = 1f; } float num2 = (val2.x - val.x) / num; float num3 = (val2.y - val.y) / num; if (num2 < 50f || num3 < 50f) { return false; } _frameX = val.x / num; _frameY = val.y / num; _frameW = num2; _frameH = num3; return true; } catch (Exception) { DropRefs(); return false; } } public void SetSuitPower(bool psHelmetWorn, bool hasPower, float charge) { _psCharge = charge; if (psHelmetWorn != _psHelmetWorn || hasPower != _psHasPower) { _psHelmetWorn = psHelmetWorn; _psHasPower = hasPower; _sig = ""; } } public void SetVisible(bool visible) { if ((Object)(object)_readoutGo != (Object)null && _readoutGo.activeSelf != visible) { _readoutGo.SetActive(visible); } } public void Populate(CharacterSnapshot data) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) string text = Signature(data); if (text == _sig) { return; } _sig = text; for (int num = _content.childCount - 1; num >= 0; num--) { Object.Destroy((Object)(object)((Component)_content.GetChild(num)).gameObject); } _sections.Clear(); _sectionIndex = 0; _pageStart = 0; _lastPageFlip = Time.unscaledTime; _headerRow = MakeRow("", PhosHeader, 9, (FontStyle)0); _headerText = ((Component)_headerRow.txt).GetComponent<Text>(); Section section = new Section { Title = "STATUS" }; foreach (ConditionEntry activeCondition in data.ActiveConditions) { if (activeCondition.Tone == ConditionTone.Warning || activeCondition.Tone == ConditionTone.Critical) { if (section.Rows.Count >= 14) { break; } string text2 = ((activeCondition.Trend == TrendDirection.Up) ? " ↗" : ((activeCondition.Trend == TrendDirection.Down) ? " ↘" : "")); bool flag = activeCondition.Tone == ConditionTone.Critical; AddRow(section, activeCondition.Name.ToUpper() + text2, ToneColor(activeCondition.Tone), 10, (FontStyle)(flag ? 1 : 0), flag && Plugin.BlinkCritical.Value); } } if (section.Rows.Count == 0) { AddRow(section, "NOMINAL", PhosDim, 10, (FontStyle)0); } _sections.Add(section); Section section2 = new Section { Title = "TRAINING" }; List<SkillEntry>[] array = new List<SkillEntry>[2] { data.TrainingSkills, data.TrainingTraits }; foreach (List<SkillEntry> list in array) { if (section2.Rows.Count >= 6) { break; } foreach (SkillEntry item in list) { if (item.RecentlyTrained) { if (section2.Rows.Count >= 6) { break; } AddRow(section2, $"{item.Name.ToUpper()} {item.TrainingPercent}%", PhosDim, 10, (FontStyle)0); } } } if (section2.Rows.Count > 0) { _sections.Add(section2); } } private static Color ToneColor(ConditionTone tone) { //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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) return (Color)(tone switch { ConditionTone.Critical => PhosCritical, ConditionTone.Warning => PhosBright, _ => PhosDim, }); } private void AddRow(Section section, string text, Color color, int size, FontStyle style, bool blink = false) { //IL_0008: 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) section.Rows.Add(MakeRow(text, color, size, style, blink)); } private Row MakeRow(string text, Color color, int size, FontStyle style, bool blink = false) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_0108: Unknown result type (might be due to invalid IL or missing references) float num = (float)size + 2f; GameObject val = new GameObject("Row"); val.layer = _uiLayer; val.transform.SetParent(_content, false); val.AddComponent<RectTransform>(); val.AddComponent<LayoutElement>().preferredHeight = num; GameObject val2 = new GameObject("Txt") { layer = _uiLayer }; val2.transform.SetParent(val.transform, false); RectTransform val3 = val2.AddComponent<RectTransform>(); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.offsetMin = Vector2.zero; val3.offsetMax = Vector2.zero; Text val4 = val2.AddComponent<Text>(); val4.text = text; val4.fontSize = size; ((Graphic)val4).color = color; val4.fontStyle = style; val4.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); val4.alignment = (TextAnchor)5; val4.horizontalOverflow = (HorizontalWrapMode)1; val4.verticalOverflow = (VerticalWrapMode)1; ((Graphic)val4).raycastTarget = false; Shadow obj = val2.AddComponent<Shadow>(); obj.effectColor = ShadowColor; obj.effectDistance = new Vector2(1f, -1f); return new Row { GO = val, txt = val3, label = val4, height = num, blink = blink }; } private void ApplyLayout(float frameOriginX, float frameW, float frameH, float canvasW) { if (_sections.Count == 0) { return; } if (_sectionIndex >= _sections.Count) { _sectionIndex = 0; } Section section = _sections[_sectionIndex]; float num = ((_lastRectH > 0f) ? _lastRectH : 400f); float num2 = Mathf.Max(0f, num - (_headerRow.height + 1f)); int num3 = (_evaTier ? Plugin.MaxRowsEVA.Value : Plugin.MaxRowsPS.Value); int num4 = Mathf.Max(1, num3 - 1); int count = section.Rows.Count; if (_pageStart >= count) { _pageStart = 0; } int num5 = 0; float num6 = 0f; for (int i = _pageStart; i < count; i++) { if (num5 >= num4) { break; } float num7 = section.Rows[i].height + 1f; if (num6 + num7 > num2 && num5 > 0) { break; } num6 += num7; num5++; } if (num5 <= 0) { num5 = 1; } int num8 = Mathf.Max(1, Mathf.CeilToInt((float)count / (float)num5)); int num9 = Mathf.Min(num8, _pageStart / num5 + 1); if ((Object)(object)_headerText != (Object)null) { string text = $"{section.Title} [{num9}/{num8}]"; if (_headerText.text != text) { _headerText.text = text; } } float rectRight = frameOriginX + (Plugin.RectX.Value + Plugin.RectW.Value) * frameW; float rectTopFrac = 1f - (Plugin.RectY.Value + Plugin.RectH.Value); float value = Plugin.EdgeInset.Value; float cursor = 0f; TraceRow(_headerRow, ref cursor, frameOriginX, frameW, frameH, rectRight, rectTopFrac, value); for (int j = 0; j < _sections.Count; j++) { Section section2 = _sections[j]; for (int k = 0; k < section2.Rows.Count; k++) { bool flag = j == _sectionIndex && k >= _pageStart && k < _pageStart + num5; Row row = section2.Rows[k]; if ((Object)(object)row.GO != (Object)null && row.GO.activeSelf != flag) { row.GO.SetActive(flag); } if (flag) { TraceRow(row, ref cursor, frameOriginX, frameW, frameH, rectRight, rectTopFrac, value); } } } if ((_sections.Count > 1 || num8 > 1) && Time.unscaledTime - _lastPageFlip >= Plugin.PageSeconds.Value) { _lastPageFlip = Time.unscaledTime; _pageStart += num5; if (_pageStart >= count) { _pageStart = 0; _sectionIndex = (_sectionIndex + 1) % _sections.Count; } } } private void TraceRow(Row row, ref float cursor, float frameOriginX, float frameW, float frameH, float rectRight, float rectTopFrac, float inward) { //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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)row.txt == (Object)null) { return; } if ((Object)(object)row.label != (Object)null) { Color color = ((Graphic)row.label).color; float num = 1f; if (row.blink && Plugin.BlinkCritical.Value) { num = ((Time.unscaledTime % 1f < 0.6f) ? 1f : 0.22f); } if (!Mathf.Approximately(color.a, num)) { color.a = num; ((Graphic)row.label).color = color; } } float num2 = cursor + row.height * 0.5f; cursor += row.height + 1f; float yFracFromTop = rectTopFrac + ((frameH > 0f) ? (num2 / frameH) : 0f); float num3 = frameOriginX + (RimXFrac(yFracFromTop) - inward) * frameW; float num4 = Mathf.Clamp(rectRight - num3, (0f - frameW) * 0.2f, frameW * 0.5f); row.txt.offsetMax = new Vector2(0f - num4, row.txt.offsetMax.y); } private static string Signature(CharacterSnapshot data) { StringBuilder stringBuilder = new StringBuilder(data.CharacterName); foreach (ConditionEntry activeCondition in data.ActiveConditions) { stringBuilder.Append('|').Append(activeCondition.Name).Append((int)activeCondition.Tone) .Append((int)activeCondition.Trend); } foreach (SkillEntry trainingSkill in data.TrainingSkills) { stringBuilder.Append('|').Append(trainingSkill.Name).Append(trainingSkill.TrainingPercent) .Append(trainingSkill.RecentlyTrained ? 'R' : '-'); } foreach (SkillEntry trainingTrait in data.TrainingTraits) { stringBuilder.Append('|').Append(trainingTrait.Name).Append(trainingTrait.TrainingPercent) .Append(trainingTrait.RecentlyTrained ? 'R' : '-'); } return stringBuilder.ToString(); } private bool EnsureRefs() { //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Invalid comparison between Unknown and I4 //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Expected O, but got Unknown //IL_0221: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_tfHelmet != (Object)null && (Object)(object)_cgHud != (Object)null) { return true; } if (Time.unscaledTime - _lastAcquireTry < 1f) { return false; } _lastAcquireTry = Time.unscaledTime; SetVanillaLampsDark(dark: false); _powerLamp?.Dispose(); _powerLamp = null; _gaugeSkin?.Restore(); _gaugeSkin = null; try { CanvasManager instance = CanvasManager.instance; GameObject val = (((Object)(object)instance != (Object)null) ? instance.goCanvasHelmet : null); if ((Object)(object)val == (Object)null) { return false; } Transform val2 = val.transform.Find("bmpHelmet"); Transform val3 = val.transform.Find("bmpHelmet/pnlHUD"); Transform val4 = val.transform.Find("bmpHelmet/bmpGauge"); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { return false; } _tfHelmet = ((Component)val2).GetComponent<RectTransform>(); _cgFrame = ((Component)val2).GetComponent<CanvasGroup>(); _cgHud = ((Component)val3).GetComponent<CanvasGroup>(); _cgGauge = (((Object)(object)val4 != (Object)null) ? ((Component)val4).GetComponent<CanvasGroup>() : null); if ((Object)(object)val4 != (Object)null) { _gaugeRoot = val4; _gaugeSkin = new GaugeSkin(val4); Transform obj = val4.Find("bmpO2"); RectTransform val5 = (RectTransform)(object)((obj is RectTransform) ? obj : null); Transform obj2 = val4.Find("bmpTemp"); RectTransform val6 = (RectTransform)(object)((obj2 is RectTransform) ? obj2 : null); if ((Object)(object)val5 != (Object)null && (Object)(object)val6 != (Object)null) { bool flag = ((Transform)val5).localPosition.y >= ((Transform)val6).localPosition.y; _lampUpper = (flag ? val5 : val6); _lampLower = (flag ? val6 : val5); } List<Graphic> list = new List<Graphic>(); List<Color> list2 = new List<Color>(); foreach (Transform item in val4) { Transform val7 = item; if (!((Object)val7).name.StartsWith("HelmetHUD_", StringComparison.Ordinal)) { Graphic component = ((Component)val7).GetComponent<Graphic>(); if (!((Object)(object)component == (Object)null)) { list.Add(component); list2.Add(component.color); } } } _vanillaLamps = list.ToArray(); _vanillaLampHome = list2.ToArray(); } _helmetCanvas = ((Component)val2).GetComponentInParent<Canvas>(); try { if ((int)_ourCanvas.renderMode == 1 && (Object)(object)_helmetCanvas != (Object)null) { Canvas val8 = (((Object)(object)CanvasManager.instance != (Object)null && (Object)(object)CanvasManager.instance.goCanvasCrewBar != (Object)null) ? CanvasManager.instance.goCanvasCrewBar.GetComponent<Canvas>() : null); int num = (((Object)(object)val8 != (Object)null) ? (val8.sortingOrder - 1) : (_helmetCanvas.sortingOrder + 1)); if (_helmetCanvas.sortingOrder >= num) { num = _helmetCanvas.sortingOrder + 1; Plugin.Log.LogWarning((object)$"[diag] helmet canvas (order {_helmetCanvas.sortingOrder}) sorts at/above the MTT slot — visor readout will draw over the MTT (order {num})."); } _ourCanvas.sortingOrder = num; Plugin.Log.LogInfo((object)string.Format("[diag] visor canvas order {0} (helmet {1}, crew bar {2}).", _ourCanvas.sortingOrder, _helmetCanvas.sortingOrder, ((Object)(object)val8 != (Object)null) ? val8.sortingOrder.ToString() : "?")); } } catch (Exception) { } Plugin.Log.LogInfo((object)"[diag] visor readout attached to helmet frame."); LogVanillaHudRects(val3, val4); return (Object)(object)_tfHelmet != (Object)null && (Object)(object)_cgHud != (Object)null; } catch (Exception ex2) { Plugin.Log.LogDebug((object)("Visor readout acquire failed: " + ex2.Message)); return false; } } private void LogVanillaHudRects(Transform hud, Transform? gauge) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_003a: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_tfHelmet == (Object)null) { return; } _tfHelmet.GetWorldCorners(_corners); Vector2 val = Vector2.op_Implicit(_corners[0]); Vector2 val2 = Vector2.op_Implicit(_corners[2]); float num = Mathf.Max(1f, val2.x - val.x); float num2 = Mathf.Max(1f, val2.y - val.y); StringBuilder stringBuilder = new StringBuilder("[diag] vanilla helmet HUD rects (frame fractions, y from bottom): "); Transform[] array = (Transform[])(object)new Transform[2] { hud, gauge }; foreach (Transform val3 in array) { if ((Object)(object)val3 == (Object)null) { continue; } foreach (Transform item in val3) { Transform val4 = item; RectTransform val5 = (RectTransform)(object)((val4 is RectTransform) ? val4 : null); if (!((Object)(object)val5 == (Object)null)) { val5.GetWorldCorners(_corners); stringBuilder.Append($"{((Object)val3).name}/{((Object)val4).name}[x {(_corners[0].x - val.x) / num:F2}-{(_corners[2].x - val.x) / num:F2}, y {(_corners[0].y - val.y) / num2:F2}-{(_corners[2].y - val.y) / num2:F2}] "); } } } Plugin.Log.LogInfo((object)stringBuilder.ToString()); } catch (Exception) { } } } } namespace OstranautsHelmetHUD.Core { internal static class DataPack { private const int Required = 1; private const string MarkerPrefix = "IsHelmetHUDData"; private static bool _reported; private static float _firstAttempt; private const float GraceSeconds = 45f; internal static bool Verified { get; private set; } internal static void VerifyOnce() { if (Verified || _reported) { return; } try { if (DataHandler.GetCond("IsHuman") == null) { return; } } catch (Exception) { return; } if (_firstAttempt <= 0f) { _firstAttempt = Time.unscaledTime; } try { if (DataHandler.GetCond("IsHelmetHUDData" + 1) != null) { Verified = true; Plugin.Log.LogInfo((object)$"Helmet HUD data pack generation {1} loaded; the helmet battery container is available."); } else { if (Time.unscaledTime - _firstAttempt < 45f) { return; } _reported = true; int num = 0; for (int i = 1; i <= 9; i++) { if (i != 1 && DataHandler.GetCond("IsHelmetHUDData" + i) != null) { num = i; break; } } if (num > 0) { Plugin.Log.LogError((object)(string.Format("VERSION MISMATCH: the Helmet HUD plugin (v{0}) needs data pack generation {1}, ", "1.0.0", 1) + $"but generation {num} is installed. The helmet battery container may be missing or wrong. " + "Reinstall both halves together — unsubscribe and resubscribe on the Workshop, or reinstall from Thunderstore — so the DLL and the data pack come from the same release.")); } else { Plugin.Log.LogError((object)"DATA PACK NOT LOADED: the Helmet HUD plugin (v1.0.0) is running, but its data pack isn't registered, so the pressure suit helmet has no battery container and cells can't be fitted. The visor readout itself still works. Fix: copy the mod's HelmetHUD folder into Ostranauts_Data/Mods/, then enable it in the in-game MODS menu (it must also appear in Ostranauts_Data/Mods/loading_order.json — the game adds it there when you enable it)."); } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Could not verify the Helmet HUD data pack (" + ex2.GetType().Name + ": " + ex2.Message + "). Continuing without the battery feature; the visor readout is unaffected.")); } } } internal static class DataPackInstaller { private const string PackName = "HelmetHUD"; internal static void Run() { try { string text = FindSource(); if (text == null) { return; } string text2 = Path.Combine(Application.dataPath, Path.Combine("Mods", "HelmetHUD")); string text3 = ReadVersion(Path.Combine(text, "mod_info.json")); string text4 = (Directory.Exists(text2) ? ReadVersion(Path.Combine(text2, "mod_info.json")) : null); if (text4 == null || !(text4 == text3)) { bool num = text4 == null && !Directory.Exists(text2); if (Directory.Exists(text2)) { Directory.Delete(text2, recursive: true); } CopyTree(text, text2); if (num) { Plugin.Log.LogWarning((object)("Installed the Helmet HUD data pack (v" + text3 + ") into Ostranauts_Data/Mods/HelmetHUD. One-time step: enable Helmet HUD in the in-game MODS menu, then restart the game. The game updates loading_order.json itself when you do.")); return; } Plugin.Log.LogInfo((object)("Helmet HUD data pack refreshed (" + (text4 ?? "unknown") + " -> " + text3 + ").")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not self-install the data pack (" + ex.GetType().Name + ": " + ex.Message + "). Manual fix: copy the mod's DataPack/HelmetHUD folder into Ostranauts_Data/Mods/, then enable Helmet HUD in the in-game MODS menu.")); } } private static string? FindSource() { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); for (int i = 0; i < 3; i++) { if (string.IsNullOrEmpty(directoryName)) { break; } string text = Path.Combine(Path.Combine(directoryName, "DataPack"), "HelmetHUD"); if (File.Exists(Path.Combine(text, "mod_info.json"))) { return text; } directoryName = Path.GetDirectoryName(directoryName); } return null; } private static string? ReadVersion(string modInfoPath) { if (!File.Exists(modInfoPath)) { return null; } Match match = Regex.Match(File.ReadAllText(modInfoPath), "\"strModVersion\"\\s*:\\s*\"([^\"]+)\""); if (!match.Success) { return null; } return match.Groups[1].Value; } private static void CopyTree(string src, string dest) { Directory.CreateDirectory(dest); string[] files = Directory.GetFiles(src); foreach (string text in files) { File.Copy(text, Path.Combine(dest, Path.GetFileName(text)), overwrite: true); } files = Directory.GetDirectories(src); foreach (string text2 in files) { CopyTree(text2, Path.Combine(dest, Path.GetFileName(text2))); } } } internal static class HelmetBattery { internal const string HelmetSlot = "head_out"; internal const string BatteryItem = "ItmBatteryDisp01"; internal const string StockedMarker = "IsHelmetHUDStocked2"; internal const string PSHelmetCond = "IsPSHelmet"; private const int CellsFitted = 2; private const int MaxAttempts = 3; private static readonly Dictionary<string, int> _attempts = new Dictionary<string, int>(StringComparer.Ordinal); private static bool _dumpedSlots; private static int CellsWanted => Plugin.CellsRequired?.Value ?? 2; internal static CondOwner? WornPSHelmet(CondOwner wearer) { try { if ((Object)(object)((wearer != null) ? wearer.compSlots : null) == (Object)null) { return null; } DumpSlotsOnce(wearer); List<CondOwner> cOs = wearer.compSlots.GetCOs("head_out", false, (CondTrigger)null); if (cOs != null) { foreach (CondOwner item in cOs) { if ((Object)(object)item != (Object)null && item.HasCond("IsPSHelmet")) { return item; } } } List<CondOwner> cOs2 = wearer.compSlots.GetCOs((string)null, false, (CondTrigger)null); if (cOs2 != null) { foreach (CondOwner item2 in cOs2) { if ((Object)(object)item2 != (Object)null && item2.HasCond("IsPSHelmet")) { return item2; } } } } catch (Exception) { } return null; } private static void DumpSlotsOnce(CondOwner wearer) { if (_dumpedSlots) { return; } _dumpedSlots = true; try { StringBuilder stringBuilder = new StringBuilder("[diag] wearer slots: "); List<Slot> slots = wearer.GetSlots(false, (SortOrder)1); if (slots == null) { stringBuilder.Append("(none)"); } else { foreach (Slot item in slots) { if (item == null) { continue; } stringBuilder.Append(item.strName).Append('['); List<CondOwner> cOs = wearer.compSlots.GetCOs(item.strName, true, (CondTrigger)null); if (cOs != null) { foreach (CondOwner item2 in cOs) { if (!((Object)(object)item2 == (Object)null)) { stringBuilder.Append(item2.strName); if (item2.HasCond("IsPSHelmet")) { stringBuilder.Append("(PS!)"); } if (item2.HasCond("IsEVAHelmet")) { stringBuilder.Append("(EVA)"); } stringBuilder.Append(' '); } } } stringBuilder.Append("] "); } } Plugin.Log.LogInfo((object)stringBuilder.ToString()); } catch (Exception ex) { Plugin.Log.LogInfo((object)("[diag] wearer slot dump failed: " + ex.GetType().Name + ": " + ex.Message)); } } internal static bool IsPowered(CondOwner helmet) { if ((Object)(object)helmet != (Object)null) { return ChargedCells(helmet) >= CellsWanted; } return false; } private static List<CondOwner> Cells(CondOwner helmet) { List<CondOwner> list = new List<CondOwner>(); try { List<CondOwner> list2 = ((helmet != null) ? helmet.GetCOs(false, (CondTrigger)null) : null); if (list2 == null) { return list; } foreach (CondOwner item in list2) { if ((Object)(object)item == (Object)null || !item.HasCond("IsBatteryDisp01")) { continue; } List<CondOwner> list3 = null; try { list3 = item.StackAsList; } catch (Exception) { } if (list3 != null && list3.Count > 0) { foreach (CondOwner item2 in list3) { if ((Object)(object)item2 != (Object)null && !list.Contains(item2)) { list.Add(item2); } } } else if (!list.Contains(item)) { list.Add(item); } } } catch (Exception) { } return list; } internal static float ChargeFraction(CondOwner helmet) { double num = 0.0; double num2 = 0.0; try { foreach (CondOwner item in Cells(helmet)) { num += item.GetCondAmount("StatPower"); num2 += item.GetCondAmount("StatPowerMax"); } } catch (Exception) { return 0f; } if (!(num2 > 0.0)) { return 0f; } return Mathf.Clamp01((float)(num / num2)); } internal static double Capacity(CondOwner helmet) { double num = 0.0; try { foreach (CondOwner item in Cells(helmet)) { num += item.GetCondAmount("StatPowerMax"); } } catch (Exception) { } return num; } internal static double NominalCapacity(CondOwner helmet) { double num = 0.0; try { foreach (CondOwner item in Cells(helmet)) { num = Math.Max(num, item.GetCondAmount("StatPowerMax")); } } catch (Exception) { } if (num <= 0.0) { return 0.0; } return num * (double)Math.Max(1, CellsWanted); } internal static void Drain(CondOwner helmet, double amount) { if ((Object)(object)helmet == (Object)null || amount <= 0.0) { return; } try { List<CondOwner> list = Cells(helmet); if (list.Count == 0) { return; } double num = amount; for (int i = 0; i < 2; i++) { if (!(num > 1E-12)) { break; } List<CondOwner> list2 = new List<CondOwner>(); foreach (CondOwner item in list) { if ((Object)(object)item != (Object)null && item.GetCondAmount("StatPower") > 0.0) { list2.Add(item); } } if (list2.Count == 0) { break; } double val = num / (double)list2.Count; foreach (CondOwner item2 in list2) { double num2 = Math.Min(item2.GetCondAmount("StatPower"), val); if (!(num2 <= 0.0)) { item2.AddCondAmount("StatPower", 0.0 - num2, 0.0, 0f); num -= num2; } } } } catch (Exception) { } } internal static int ChargedCells(CondOwner helmet) { int num = 0; try { foreach (CondOwner item in Cells(helmet)) { if (item.GetCondAmount("StatPower") > 0.0) { num++; } } } catch (Exception) { } return num; } internal static bool StockIfNeeded(CondOwner helmet) { try { if ((Object)(object)helmet == (Object)null || helmet.HasCond("IsHelmetHUDStocked2")) { return false; } int num = CountCells(helmet); if (num >= 2) { helmet.AddCondAmount("IsHelmetHUDStocked2", 1.0, 0.0, 0f); return false; } string key = helmet.strID ?? "?"; _attempts.TryGetValue(key, out var value); if (value >= 3) { return false; } _attempts[key] = value + 1; int num2 = 0; for (int i = num; i < 2; i++) { CondOwner condOwner = DataHandler.GetCondOwner("ItmBatteryDisp01"); if ((Object)(object)condOwner == (Object)null) { Plugin.Log.LogWarning((object)("Couldn't create a 'ItmBatteryDisp01' to fit to helmet '" + helmet.strID + "' — the game doesn't know that item. This usually means another mod has replaced or removed the vanilla BB battery. The helmet keeps its container; fit cells by hand and everything else works normally.")); break; } if ((Object)(object)helmet.AddCO(condOwner, false, true, true) != (Object)null) { Plugin.Log.LogWarning((object)($"Helmet '{helmet.strID}' wouldn't accept a BB cell (fitted {num2} of {2}). " + "Its battery container is probably missing or already full, which happens when another mod also redefines OutfitHelmet02 and loads after this one. Check the load order in Ostranauts_Data/Mods/loading_order.json — Helmet HUD needs to come after any other mod that touches the helmet.")); break; } num2++; } if (CountCells(helmet) >= 2) { helmet.AddCondAmount("IsHelmetHUDStocked2", 1.0, 0.0, 0f); Plugin.Log.LogInfo((object)$"Fitted {num2} BB cell(s) to helmet '{helmet.strID}', which predates this mod's battery container. Won't happen again for this helmet."); } return num2 > 0; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Helmet cell retrofit failed for '" + (((helmet != null) ? helmet.strID : null) ?? "unknown") + "' (" + ex.GetType().Name + ": " + ex.Message + "). The helmet is left as-is and won't be retried; fit cells by hand if its container is empty. The visor readout is unaffected.")); return false; } } private static int CountCells(CondOwner helmet) { return Cells(helmet).Count; } } public class HelmetController : MonoBehaviour { private const float RefreshInterval = 0.5f; private VisorReadout? _visor; private CharacterSnapshot? _lastData; private float _timer; private bool _psHelmetWorn; private bool _psHasPower; private float _psCharge; private double _lastDrainEpoch = -1.0; private const double MaxDrainStep = 600.0; private VisorGlow? _glow; public static HelmetController? Instance { get; private set; } private void Awake() { if ((Object)(object)Instance != (Object)null) { Object.Destroy((Object)(object)((Component)this).gameObject); } else { Instance = this; } } private void Update() { _timer += Time.unscaledDeltaTime; if (!(_timer < 0.5f)) { _timer = 0f; _lastData = CharacterDataReader.ReadPlayer(); if (_lastData == null && (Object)(object)_visor != (Object)null) { Object.Destroy((Object)(object)((Component)_visor).gameObject); _visor = null; } DataPack.VerifyOnce(); RetrofitWornHelmet(); ReadSuitPower(); } } private void ReadSuitPower() { try { CondOwner selectedCrew = CrewSim.GetSelectedCrew(); CondOwner val = (((Object)(object)selectedCrew != (Object)null) ? HelmetBattery.WornPSHelmet(selectedCrew) : null); _psHelmetWorn = (Object)(object)val != (Object)null; if ((Object)(object)val != (Object)null) { DrainWornHelmet(val); } _psCharge = (((Object)(object)val != (Object)null) ? HelmetBattery.ChargeFraction(val) : 0f); _psHasPower = (Object)(object)val != (Object)null && (!Plugin.RequireBatteries.Value || HelmetBattery.IsPowered(val)); } catch (Exception) { _psHelmetWorn = false; _psHasPower = false; _psCharge = 0f; } } private void DrainWornHelmet(CondOwner helmet) { if (!Plugin.RequireBatteries.Value) { return; } float value = Plugin.CellHours.Value; if (value <= 0f) { return; } double fEpoch; try { fEpoch = StarSystem.fEpoch; } catch (Exception) { return; } if (_lastDrainEpoch < 0.0) { _lastDrainEpoch = fEpoch; return; } double num = fEpoch - _lastDrainEpoch; _lastDrainEpoch = fEpoch; if (!(num <= 0.0) && !(num > 600.0)) { double num2 = HelmetBattery.NominalCapacity(helmet); if (!(num2 <= 0.0)) { HelmetBattery.Drain(helmet, num2 * (num / ((double)value * 3600.0))); } } } private void RetrofitWornHelmet() { if (!Plugin.StockOldHelmets.Value || !DataPack.Verified) { return; } try { CondOwner selectedCrew = CrewSim.GetSelectedCrew(); if (!((Object)(object)selectedCrew == (Object)null)) { CondOwner val = HelmetBattery.WornPSHelmet(selectedCrew); if ((Object)(object)val != (Object)null) { HelmetBattery.StockIfNeeded(val); } } } catch (Exception) { } } private void LateUpdate() { if (!Plugin.Enabled.Value) { _visor?.Suspend(); Publish(helmetOn: false, readoutActive: false); return; } if (_lastData == null) { Publish(helmetOn: false, readoutActive: false); return; } if ((Object)(object)_visor == (Object)null) { _visor = VisorReadout.Create(); } _visor.SetSuitPower(_psHelmetWorn, _psHasPower, _psCharge); bool flag = !MenuOpen() && _visor.UpdateAnchor(); if (flag) { _visor.SetVisible(visible: true); _visor.Populate(_lastData); } else { _visor.SetVisible(visible: false); } Publish(_visor.HelmetVisible, flag); DriveGlow(flag, _visor.EvaTier); } private void DriveGlow(bool readoutLive, bool evaTier) { try { float value = Plugin.GlowRadius.Value; object obj; if (!readoutLive || !(value > 0f)) { obj = null; } else { CondOwner selectedCrew = CrewSim.GetSelectedCrew(); obj = ((selectedCrew != null) ? selectedCrew.Crew : null); } Crew val = (Crew)obj; if (_glow != null && ((Object)(object)val == (Object)null || (Object)(object)_glow.Owner != (Object)(object)val)) { _glow.Dispose(); _glow = null; } if (!((Object)(object)val == (Object)null)) { if (_glow == null) { _glow = new VisorGlow(val); } _glow.Set(on: true, evaTier, value); _glow.Follow(); } } catch (Exception) { } } private static void Publish(bool helmetOn, bool readoutActive) { HelmetLink.HelmetOn = helmetOn; HelmetLink.ReadoutActive = readoutActive; } private void OnDisable() { Publish(helmetOn: false, readoutActive: false); } private static bool MenuOpen() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 try { CanvasManager instance = CanvasManager.instance; if ((Object)(object)instance == (Object)null) { return false; } GUIState state = instance.State; return (int)state == 3 || (int)state == 4 || (int)state == 2 || (int)state == 5 || (int)state == 6 || (int)state == 7 || CanvasManager.IsCanvasQuitShowing(); } catch (Exception) { return false; } } public void Teardown() { _glow?.Dispose(); _glow = null; if ((Object)(object)_visor != (Object)null) { Object.Destroy((Object)(object)((Component)_visor).gameObject); } _visor = null; Instance = null; } } internal sealed class VisorGlow { internal const string LightName = "HelmetHUDVisor"; private const string ColorPS = "HelmetHUDVisorPS"; private const string ColorEVA = "HelmetHUDVisorEVA"; private const string HeadBone = "RotationAdjuster/Armature/Hips/LowerSpine/Chest/Neck/Head"; private const string Cookie = "ItmLitSphere01"; private readonly Crew _crew; private readonly Visibility? _vis; private readonly Transform? _tfLight; private bool _lit; private bool _evaLast; private bool _colourSet; internal bool Valid { get { if ((Object)(object)_vis != (Object)null) { return (Object)(object)_tfLight != (Object)null; } return false; } } internal Crew Owner => _crew; internal VisorGlow(Crew crew) { _crew = crew; try { JsonLight light = DataHandler.GetLight("HelmetHUDVisor"); if (light == null) { Plugin.Log.LogWarning((object)"The visor glow needs light 'HelmetHUDVisor', which isn't registered. That comes from the mod's data pack (data/lights/), so this is the same half-installed state the data-pack check reports — the readout and the battery still work, there's just no glow."); return; } Transform val = ((Component)crew).transform.Find("RotationAdjuster/Armature/Hips/LowerSpine/Chest/Neck/Head") ?? ((Component)crew).transform; Visibility vis = default(Visibility); Transform tfLight = default(Transform); crew.AddLight(light, "ItmLitSphere01", val, ref vis, ref tfLight); _vis = vis; _tfLight = tfLight; Plugin.Log.LogInfo((object)("[diag] visor glow attached to " + (((Object)(object)val == (Object)(object)((Component)crew).transform) ? "crew root (head bone not found)" : "head bone") + ".")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't build the visor glo