Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Player Status Bars v0.3.0
BepInEx/plugins/PlayerStatusBars/PlayerStatusBars.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 BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using LethalConfig; using LethalConfig.ConfigItems; using LethalConfig.ConfigItems.Options; using Microsoft.CodeAnalysis; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [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 OtherPlayerStatusBars { internal static class ChineseProjectDetection { public const string PluginGuid = "Aueser.LCChineseProject"; public const string LegacyPluginGuid = "cn.codex.v81testchn"; public const string ThunderstoreDirectoryName = "Aueser-LC_Chinese_Project"; public const string PluginFileName = "V81TestChn.dll"; public static bool Matches(string? guid, string? location) { if (!string.Equals(guid, "Aueser.LCChineseProject", StringComparison.OrdinalIgnoreCase) && !string.Equals(guid, "cn.codex.v81testchn", StringComparison.OrdinalIgnoreCase) && !Contains(location, "Aueser-LC_Chinese_Project")) { return Contains(location, "V81TestChn.dll"); } return true; } private static bool Contains(string? value, string term) { if (value != null) { return value.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } internal static class LegacyConfigCleaner { private static readonly string[] ObsoleteKeys = new string[9] { "Bar Spacing", "Health Bar Y Offset", "Infection Bar Y Offset", "Infection Bar Display Mode", "Critical Health Sync Mode", "Show Health Text", "Show Infection Text", "Enable Ten Health Fallback", "Ten Health Fallback Delay Seconds" }; public static bool TryRemoveObsoleteKeys(string[] lines, out string[] cleanedLines) { List<string> list = new List<string>(lines.Length); foreach (string text in lines) { if (!IsObsoleteEntry(text)) { list.Add(text); } } cleanedLines = list.ToArray(); return cleanedLines.Length != lines.Length; } private static bool IsObsoleteEntry(string line) { int num = line.IndexOf('='); if (num <= 0 || line.TrimStart().StartsWith("#", StringComparison.Ordinal)) { return false; } string value = line.Substring(0, num).Trim(); return Array.IndexOf(ObsoleteKeys, value) >= 0; } } internal static class LethalConfigIntegration { public const string PluginGuid = "ainavt.lc.lethalconfig"; [MethodImpl(MethodImplOptions.NoInlining)] public static void Register(StatusBarConfig settings, bool useChinese) { LethalConfigManager.SkipAutoGen(); LethalConfigManager.SetModDescription(useChinese ? "在其他玩家头顶显示血条和感染条。" : "Shows health and infection bars above other players."); AddBool(settings.EnabledEntry, in StatusBarConfigText.Enabled, useChinese); AddBool(settings.HideInOrbitEntry, in StatusBarConfigText.HideInOrbit, useChinese); AddFloat(settings.MaxDistanceEntry, in StatusBarConfigText.MaxDisplayDistance, useChinese); AddBool(settings.ShowInfectionBarsEntry, in StatusBarConfigText.ShowInfectionBars, useChinese); AddBarStyle(settings.BarStyleEntry, in StatusBarConfigText.BarStyle, useChinese); AddFloat(settings.HeadOffsetEntry, in StatusBarConfigText.HeadOffset, useChinese); AddFloat(settings.UiScaleEntry, in StatusBarConfigText.UiScale, useChinese); AddBool(settings.ShowTextEntry, in StatusBarConfigText.ShowText, useChinese); AddColor(settings.HealthColorEntry, in StatusBarConfigText.HealthBarColor, useChinese); AddColor(settings.InfectionColorEntry, in StatusBarConfigText.InfectionBarColor, useChinese); AddColor(settings.BackgroundColorEntry, in StatusBarConfigText.BackgroundColor, useChinese); AddBool(settings.DebugLoggingEntry, in StatusBarConfigText.DebugLogging, useChinese); } private static void AddBool(ConfigEntry<bool> entry, in ConfigText text, bool useChinese) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(entry, new BoolCheckBoxOptions { Section = text.DisplaySection(useChinese), Name = text.DisplayName(useChinese), Description = text.DisplayDescription(useChinese), RequiresRestart = false })); } private static void AddFloat(ConfigEntry<float> entry, in ConfigText text, bool useChinese) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(entry, new FloatSliderOptions { Section = text.DisplaySection(useChinese), Name = text.DisplayName(useChinese), Description = text.DisplayDescription(useChinese), RequiresRestart = false })); } private static void AddBarStyle(ConfigEntry<StatusBarConfig.BarStyle> entry, in ConfigText text, bool useChinese) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem<StatusBarConfig.BarStyle>(entry, new EnumDropDownOptions { Section = text.DisplaySection(useChinese), Name = text.DisplayName(useChinese), Description = text.DisplayDescription(useChinese), RequiresRestart = false })); } private static void AddColor(ConfigEntry<StatusBarConfig.ColorPreset> entry, in ConfigText text, bool useChinese) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem<StatusBarConfig.ColorPreset>(entry, new EnumDropDownOptions { Section = text.DisplaySection(useChinese), Name = text.DisplayName(useChinese), Description = text.DisplayDescription(useChinese), RequiresRestart = false })); } } internal sealed class StatusBarConfig { public enum BarStyle { Classic, Segmented } public enum ColorPreset { Green, Red, Orange, Yellow, Blue, Cyan, White, Black, Slate } public static readonly Color SegmentedCriticalColor = new Color(0.74f, 0.32f, 0.3f, 1f); private const float AnchorSpacingUnits = 14f; private readonly bool useChineseDescriptions; private Color cachedHealthFillColor; private Color cachedInfectionFillColor; private Color cachedSegmentedHealthFillColor; private Color cachedSegmentedInfectionFillColor; private Color cachedBackgroundColor; private Color cachedBorderColor; private float maxDistanceSqr; private float anchorYOffset; public ConfigEntry<bool> EnabledEntry { get; } public ConfigEntry<bool> HideInOrbitEntry { get; } public ConfigEntry<float> MaxDistanceEntry { get; } public ConfigEntry<bool> ShowInfectionBarsEntry { get; } public ConfigEntry<BarStyle> BarStyleEntry { get; } public ConfigEntry<float> HeadOffsetEntry { get; } public ConfigEntry<float> UiScaleEntry { get; } public ConfigEntry<bool> ShowTextEntry { get; } public ConfigEntry<ColorPreset> HealthColorEntry { get; } public ConfigEntry<ColorPreset> InfectionColorEntry { get; } public ConfigEntry<ColorPreset> BackgroundColorEntry { get; } public ConfigEntry<bool> DebugLoggingEntry { get; } public bool Enabled => EnabledEntry.Value; public bool HideInOrbit => HideInOrbitEntry.Value; public float MaxDistance => MaxDistanceEntry.Value; public float MaxDistanceSqr => maxDistanceSqr; public float AnchorYOffset => anchorYOffset; public float UiScale => UiScaleEntry.Value; public bool ShowText => ShowTextEntry.Value; public BarStyle Style => BarStyleEntry.Value; public bool ShowInfectionBars => ShowInfectionBarsEntry.Value; public bool DebugLogging => DebugLoggingEntry.Value; public int Revision { get; private set; } public event Action? SettingsChanged; private StatusBarConfig(ConfigFile configFile, bool useChineseDescriptions) { this.useChineseDescriptions = useChineseDescriptions; EnabledEntry = Bind(configFile, in StatusBarConfigText.Enabled, defaultValue: true); HideInOrbitEntry = Bind(configFile, in StatusBarConfigText.HideInOrbit, defaultValue: true); MaxDistanceEntry = Bind(configFile, in StatusBarConfigText.MaxDisplayDistance, 18f, (AcceptableValueBase?)(object)new AcceptableValueRange<float>(2f, 80f)); ShowInfectionBarsEntry = Bind(configFile, in StatusBarConfigText.ShowInfectionBars, defaultValue: true); BarStyleEntry = Bind(configFile, in StatusBarConfigText.BarStyle, BarStyle.Segmented); HeadOffsetEntry = Bind(configFile, in StatusBarConfigText.HeadOffset, 0.65f, (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 2f)); UiScaleEntry = Bind(configFile, in StatusBarConfigText.UiScale, 0.0085f, (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.003f, 0.05f)); ShowTextEntry = Bind(configFile, in StatusBarConfigText.ShowText, defaultValue: true); HealthColorEntry = Bind(configFile, in StatusBarConfigText.HealthBarColor, ColorPreset.Green); InfectionColorEntry = Bind(configFile, in StatusBarConfigText.InfectionBarColor, ColorPreset.Orange); BackgroundColorEntry = Bind(configFile, in StatusBarConfigText.BackgroundColor, ColorPreset.Slate); DebugLoggingEntry = Bind(configFile, in StatusBarConfigText.DebugLogging, defaultValue: false); UpdateCachedValues(); } public static StatusBarConfig Create(ConfigFile configFile, bool useChineseDescriptions) { return new StatusBarConfig(configFile, useChineseDescriptions); } public Color GetHealthFillColor() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return cachedHealthFillColor; } public Color GetInfectionFillColor() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return cachedInfectionFillColor; } public Color GetSegmentedHealthFillColor() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return cachedSegmentedHealthFillColor; } public Color GetSegmentedInfectionFillColor() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return cachedSegmentedInfectionFillColor; } public Color GetBackgroundColor() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return cachedBackgroundColor; } public Color GetBorderColor() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return cachedBorderColor; } private ConfigEntry<T> Bind<T>(ConfigFile configFile, in ConfigText text, T defaultValue, AcceptableValueBase? acceptableValues = null) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(text.DisplayDescription(useChineseDescriptions), acceptableValues, Array.Empty<object>()); ConfigEntry<T> obj = configFile.Bind<T>(text.Section, text.Key, defaultValue, val); obj.SettingChanged += delegate { NotifySettingsChanged(); }; return obj; } private void NotifySettingsChanged() { UpdateCachedValues(); Revision++; this.SettingsChanged?.Invoke(); } private void UpdateCachedValues() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) cachedHealthFillColor = ResolveColor(HealthColorEntry.Value, 0.95f); cachedInfectionFillColor = ResolveColor(InfectionColorEntry.Value, 0.95f); cachedSegmentedHealthFillColor = ResolveSegmentedColor(HealthColorEntry.Value); cachedSegmentedInfectionFillColor = ResolveSegmentedColor(InfectionColorEntry.Value); cachedBackgroundColor = ResolveColor(BackgroundColorEntry.Value, 0.78f); cachedBorderColor = cachedBackgroundColor; cachedBorderColor.a = 1f; cachedBorderColor = Color.Lerp(cachedBorderColor, Color.white, 0.35f); float value = MaxDistanceEntry.Value; maxDistanceSqr = value * value; anchorYOffset = HeadOffsetEntry.Value + 14f * UiScaleEntry.Value; } private static Color ResolveSegmentedColor(ColorPreset preset) { //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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) return (Color)(preset switch { ColorPreset.Green => new Color(0.44f, 0.64f, 0.46f, 1f), ColorPreset.Red => SegmentedCriticalColor, ColorPreset.Orange => new Color(0.755f, 0.512f, 0.305f, 1f), ColorPreset.Yellow => new Color(0.78f, 0.7f, 0.36f, 1f), ColorPreset.Blue => new Color(0.38f, 0.54f, 0.76f, 1f), ColorPreset.Cyan => new Color(0.36f, 0.68f, 0.7f, 1f), ColorPreset.White => new Color(0.85f, 0.85f, 0.85f, 1f), ColorPreset.Black => new Color(0.12f, 0.12f, 0.12f, 1f), _ => new Color(0.22f, 0.25f, 0.3f, 1f), }); } private static Color ResolveColor(ColorPreset preset, float alpha) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: 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_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) Color result = (Color)(preset switch { ColorPreset.Green => new Color(0.18f, 0.86f, 0.32f, alpha), ColorPreset.Red => new Color(0.88f, 0.25f, 0.22f, alpha), ColorPreset.Orange => new Color(0.93f, 0.47f, 0.16f, alpha), ColorPreset.Yellow => new Color(0.95f, 0.82f, 0.18f, alpha), ColorPreset.Blue => new Color(0.28f, 0.58f, 0.92f, alpha), ColorPreset.Cyan => new Color(0.18f, 0.83f, 0.88f, alpha), ColorPreset.White => new Color(0.95f, 0.95f, 0.95f, alpha), ColorPreset.Black => new Color(0.08f, 0.08f, 0.08f, alpha), _ => new Color(0.12f, 0.15f, 0.2f, alpha), }); result.a = alpha; return result; } } internal readonly struct ConfigText { public string Section { get; } public string Key { get; } public string Description { get; } public string ChineseSection { get; } public string ChineseName { get; } public string ChineseDescription { get; } public ConfigText(string section, string key, string description, string chineseSection, string chineseName, string chineseDescription) { Section = section; Key = key; Description = description; ChineseSection = chineseSection; ChineseName = chineseName; ChineseDescription = chineseDescription; } public string DisplaySection(bool useChinese) { if (!useChinese) { return Section; } return ChineseSection; } public string DisplayName(bool useChinese) { if (!useChinese) { return Key; } return ChineseName; } public string DisplayDescription(bool useChinese) { if (!useChinese) { return Description; } return ChineseDescription; } } internal static class StatusBarConfigText { public const string ModDescription = "Shows health and infection bars above other players."; public const string ChineseModDescription = "在其他玩家头顶显示血条和感染条。"; private const string General = "General"; private const string Layout = "Layout"; private const string Text = "Text"; private const string Colors = "Colors"; private const string Debug = "Debug"; private const string ChineseGeneral = "常规"; private const string ChineseLayout = "布局"; private const string ChineseText = "文字"; private const string ChineseColors = "颜色"; private const string ChineseDebug = "调试"; private const string ChineseColorOptions = "可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。"; public static readonly ConfigText Enabled = new ConfigText("General", "Enabled", "Show status bars above other players. Status sync keeps running while this is off, so other players still receive your data.", "常规", "启用状态条", "在其他玩家头顶显示状态条。关闭后状态同步仍会运行,其他玩家依然能收到你的数据。"); public static readonly ConfigText HideInOrbit = new ConfigText("General", "Hide In Orbit", "Hide status bars while the ship is in orbit.", "常规", "轨道中隐藏", "飞船处于轨道时隐藏状态条。"); public static readonly ConfigText MaxDisplayDistance = new ConfigText("General", "Max Display Distance", "Hide status bars beyond this distance.", "常规", "最大显示距离", "超过此距离时隐藏状态条。"); public static readonly ConfigText ShowInfectionBars = new ConfigText("General", "Show Infection Bars", "Host setting for the whole room. Infection bars only appear when the host runs this version of the mod with this turned on; the value on other players has no effect.", "常规", "显示感染条", "主机设置,对整个房间生效。只有主机安装了此版本模组并开启此项时才显示感染条,其他玩家的设置不起作用。"); public static readonly ConfigText BarStyle = new ConfigText("Layout", "Bar Style", "Look of the status bars. Segmented shows one compact panel with ten-cell bars and animated damage and healing. Classic is the original style.", "布局", "状态条样式", "状态条的外观。Segmented 为十格样式,使用合并面板,并带有掉血和回血动画;Classic 为经典样式。"); public static readonly ConfigText HeadOffset = new ConfigText("Layout", "Head Offset", "Height of the status bars above the player's head.", "布局", "头顶高度", "状态条距离玩家头顶的高度。"); public static readonly ConfigText UiScale = new ConfigText("Layout", "UI Scale", "Size of the status bars.", "布局", "界面缩放", "状态条的大小。"); public static readonly ConfigText ShowText = new ConfigText("Text", "Show Text", "Show health numbers and infection percentage on the bars.", "文字", "显示文字", "在状态条上显示血量数字和感染百分比。"); public static readonly ConfigText HealthBarColor = new ConfigText("Colors", "Health Bar Color", "Fill color of the health bar.", "颜色", "血条颜色", "血条的填充颜色。可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。"); public static readonly ConfigText InfectionBarColor = new ConfigText("Colors", "Infection Bar Color", "Fill color of the infection bar.", "颜色", "感染条颜色", "感染条的填充颜色。可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。"); public static readonly ConfigText BackgroundColor = new ConfigText("Colors", "Background Color", "Background color of both bars.", "颜色", "背景颜色", "血条和感染条的背景颜色。可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。"); public static readonly ConfigText DebugLogging = new ConfigText("Debug", "Debug Logging", "Write throttled diagnostic logs for bar creation, filtering, visibility, camera state and health display decisions.", "调试", "调试日志", "写入节流诊断日志,用于排查状态条的创建、过滤、可见性、相机状态和血量显示决策。"); public static readonly ConfigText[] All = new ConfigText[12] { Enabled, HideInOrbit, MaxDisplayDistance, ShowInfectionBars, BarStyle, HeadOffset, UiScale, ShowText, HealthBarColor, InfectionBarColor, BackgroundColor, DebugLogging }; } internal static class MyPluginInfo { public const string PluginGuid = "playerstatusbars"; public const string PluginName = "PlayerStatusBars"; public const string PluginVersion = "0.3.0"; } internal sealed class PlayerStatusBarManager : MonoBehaviour { private const float DebugSummaryInterval = 5f; private const float ActiveRefreshInterval = 0.5f; private const float IdleRefreshInterval = 2.5f; private const int ScanSlotsPerRefresh = 4; private const int MinimumPlayerSlotCapacity = 16; private PlayerStatusBarView?[] trackedBarsBySlot = Array.Empty<PlayerStatusBarView>(); private PlayerStatusBarView?[] activeBars = Array.Empty<PlayerStatusBarView>(); private int activeBarCount; private int activePlayerSlotCount; private readonly Stack<PlayerStatusBarView> pooledBars = new Stack<PlayerStatusBarView>(); private float nextRefreshTime; private float nextDebugSummaryTime; private int refreshScanCursor; private bool displaySuspended; private void Start() { Plugin.LogDebug($"Runtime manager started active={((Component)this).gameObject.activeInHierarchy}."); } private void Update() { StatusBarConfig settings = Plugin.Settings; StartOfRound instance = StartOfRound.Instance; PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : instance?.localPlayerController); PlayerControllerB[] array = instance?.allPlayerScripts; PlayerStatusSnapshotSync.Tick(instance, array); if (!settings.Enabled) { LogDebugBlocked("disabled"); SuspendDisplay(); return; } if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null || array == null) { if (settings.DebugLogging) { LogDebugBlocked($"not-ready startOfRound={(Object)(object)instance != (Object)null} localPlayer={(Object)(object)val != (Object)null} allPlayers={array != null}"); } SuspendDisplay(); return; } EnsureSlotCapacity(array.Length); if (ShouldSkipBarsForShipState(instance)) { if (settings.DebugLogging) { LogDebugBlocked($"orbit-hidden inShipPhase={instance.inShipPhase} shipHasLanded={instance.shipHasLanded} shipDoorsEnabled={instance.shipDoorsEnabled} shipIsLeaving={instance.shipIsLeaving} dayStarted={TimeOfDay.Instance?.currentDayTimeStarted}"); } SuspendDisplay(); return; } ResumeDisplay(); if (activeBarCount != 0 || !(Time.unscaledTime < nextRefreshTime)) { if (Time.unscaledTime >= nextRefreshTime) { RefreshPlayerSlice(instance, val, array); } if (activeBarCount != 0) { TickTrackedBars(CreateFrameContext(settings, val)); } } } private void SuspendDisplay() { if (!displaySuspended) { displaySuspended = true; ClearBars(); PlayerStatusSnapshotSync.ClearSnapshots(); CadaverGrowthInfectionProvider.InvalidateAll(); } } private void ResumeDisplay() { if (displaySuspended) { displaySuspended = false; nextRefreshTime = 0f; CadaverGrowthInfectionProvider.InvalidateAll(); } } private void RefreshPlayerSlice(StartOfRound startOfRound, PlayerControllerB localPlayer, PlayerControllerB[] allPlayers) { bool flag = Plugin.Settings.DebugLogging && Time.unscaledTime >= nextDebugSummaryTime; int num = 0; int num2 = activeBarCount; int num3 = 0; if (allPlayers.Length == 0) { ClearBars(); LogDebugSummary(startOfRound, localPlayer, 0, num, num2, num3); ScheduleNextRefresh(num > 0 || num2 > 0 || allPlayers.Length > 4); return; } int num4 = Mathf.Min(4, allPlayers.Length); for (int i = 0; i < num4; i++) { int num5 = refreshScanCursor; refreshScanCursor = (refreshScanCursor + 1) % allPlayers.Length; PlayerControllerB val = allPlayers[num5]; if (!ShouldTrackPlayer(startOfRound, val, localPlayer, num5, allPlayers, out string skipReason)) { num3++; if (flag) { LogDebugPlayerSkip(num5, val, skipReason); } RemoveBar(num5); continue; } int num6 = ResolvePlayerKey(val, num5, allPlayers); PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[num5]; if (!((Object)(object)playerStatusBarView != (Object)null) || !playerStatusBarView.MatchesPlayer(val, localPlayer, num6)) { if ((Object)(object)playerStatusBarView != (Object)null) { RemoveBar(num5); } PlayerStatusBarView playerStatusBarView2 = RentBarView(num6, val); if ((Object)(object)playerStatusBarView2 == (Object)null) { Plugin.Log.LogWarning((object)$"Failed to create player status bar for playerKey={num6}."); continue; } trackedBarsBySlot[num5] = playerStatusBarView2; AddActiveBar(playerStatusBarView2); num++; Plugin.LogDebug($"Created bar playerKey={num6} slot={num5} clientId={val.playerClientId} actualClientId={val.actualClientId} name='{val.playerUsername}' health={val.health} active={((Component)val).gameObject.activeInHierarchy}."); } } LogDebugSummary(startOfRound, localPlayer, allPlayers.Length, num, num2, num3); ScheduleNextRefresh(num > 0 || num2 > 0 || allPlayers.Length > 4); } private void EnsureSlotCapacity(int slotCount) { if (slotCount < activePlayerSlotCount) { ReleaseBarsFrom(slotCount); } activePlayerSlotCount = slotCount; if (slotCount == 0 || refreshScanCursor >= slotCount) { refreshScanCursor = 0; } if (trackedBarsBySlot.Length < slotCount) { int num = trackedBarsBySlot.Length; PlayerStatusBarView[] destinationArray = new PlayerStatusBarView[GrowSlotCapacity(slotCount)]; if (num > 0) { Array.Copy(trackedBarsBySlot, destinationArray, num); } trackedBarsBySlot = destinationArray; } } private static int GrowSlotCapacity(int requiredCapacity) { int num; for (num = 16; num < requiredCapacity; num *= 2) { } return num; } private void ScheduleNextRefresh(bool hasActiveBars) { nextRefreshTime = Time.unscaledTime + (hasActiveBars ? 0.5f : 2.5f); } private PlayerStatusBarView? RentBarView(int playerKey, PlayerControllerB player) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown PlayerStatusBarView playerStatusBarView; if (pooledBars.Count > 0) { playerStatusBarView = pooledBars.Pop(); ((Component)playerStatusBarView).transform.SetParent(((Component)this).transform, false); ((Object)playerStatusBarView).name = $"OtherPlayerStatusBar_{playerKey}"; playerStatusBarView.Bind(player, playerKey); return playerStatusBarView; } GameObject val = new GameObject($"OtherPlayerStatusBar_{playerKey}"); val.transform.SetParent(((Component)this).transform, false); playerStatusBarView = val.AddComponent<PlayerStatusBarView>(); if (playerStatusBarView.Initialize(player, playerKey)) { return playerStatusBarView; } Object.Destroy((Object)(object)val); return null; } private void ReleaseBarView(PlayerStatusBarView view) { view.Release(); ((Component)view).transform.SetParent(((Component)this).transform, false); pooledBars.Push(view); } private void TickTrackedBars(in PlayerStatusFrameContext frameContext) { for (int i = 0; i < activeBarCount; i++) { PlayerStatusBarView playerStatusBarView = activeBars[i]; if ((Object)(object)playerStatusBarView != (Object)null) { playerStatusBarView.Tick(in frameContext); } } } private void AddActiveBar(PlayerStatusBarView view) { EnsureActiveBarCapacity(activeBarCount + 1); activeBars[activeBarCount] = view; activeBarCount++; } private void RemoveActiveBar(PlayerStatusBarView view) { for (int i = 0; i < activeBarCount; i++) { if (!((Object)(object)activeBars[i] != (Object)(object)view)) { int num = activeBarCount - 1; activeBars[i] = activeBars[num]; activeBars[num] = null; activeBarCount = num; break; } } } private void EnsureActiveBarCapacity(int requiredCapacity) { if (activeBars.Length < requiredCapacity) { PlayerStatusBarView[] destinationArray = new PlayerStatusBarView[GrowSlotCapacity(requiredCapacity)]; if (activeBarCount > 0) { Array.Copy(activeBars, destinationArray, activeBarCount); } activeBars = destinationArray; } } private static PlayerStatusFrameContext CreateFrameContext(StatusBarConfig settings, PlayerControllerB localPlayer) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) Camera viewCamera = StatusBarBillboard.ResolveViewCamera(); Quaternion rotation; bool hasBillboardRotation = StatusBarBillboard.TryResolveBillboardRotation(viewCamera, out rotation); return new PlayerStatusFrameContext(settings, localPlayer, viewCamera, hasBillboardRotation, rotation, ResolveObserverPosition(localPlayer, viewCamera)); } private static Vector3 ResolveObserverPosition(PlayerControllerB localPlayer, Camera? viewCamera) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)viewCamera != (Object)null) { return ((Component)viewCamera).transform.position; } PlayerControllerB spectatedPlayerScript = localPlayer.spectatedPlayerScript; if (localPlayer.isPlayerDead && (Object)(object)spectatedPlayerScript != (Object)null && !spectatedPlayerScript.isPlayerDead) { return ((Component)spectatedPlayerScript).transform.position; } return ((Component)localPlayer).transform.position; } private static bool ShouldTrackPlayer(StartOfRound startOfRound, PlayerControllerB player, PlayerControllerB localPlayer, int slot, PlayerControllerB[] allPlayers, out string skipReason) { if ((Object)(object)player == (Object)null || (Object)(object)localPlayer == (Object)null) { skipReason = "null-player-or-local"; return false; } if ((Object)(object)player == (Object)(object)localPlayer) { skipReason = "local-player"; return false; } if (!((Component)player).gameObject.activeInHierarchy) { skipReason = "inactive-gameobject"; return false; } int playerKey = ResolvePlayerKey(player, slot, allPlayers); if (!IsConnectedPlayerSlot(startOfRound, player, playerKey)) { skipReason = "not-connected"; return false; } skipReason = string.Empty; return true; } private static bool IsConnectedPlayerSlot(StartOfRound startOfRound, PlayerControllerB player, int playerKey) { if (startOfRound.ClientPlayerList.TryGetValue(player.actualClientId, out var value)) { return value == playerKey; } if (player.isPlayerControlled) { return !player.disconnectedMidGame; } return false; } private static int ResolvePlayerKey(PlayerControllerB player, int slotIndex, PlayerControllerB[] allPlayers) { int num = (int)player.playerClientId; if (num >= 0 && num < allPlayers.Length && (Object)(object)allPlayers[num] == (Object)(object)player) { return num; } return slotIndex; } internal static bool ShouldSkipBarsForShipState(StartOfRound startOfRound) { if (!Plugin.Settings.HideInOrbit) { return false; } if (startOfRound.shipIsLeaving) { return true; } if ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.currentDayTimeStarted) { return false; } if (startOfRound.inShipPhase) { return !startOfRound.shipHasLanded; } return false; } private void RemoveBar(int playerId) { if (playerId < 0 || playerId >= trackedBarsBySlot.Length) { return; } PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[playerId]; if (!((Object)(object)playerStatusBarView == (Object)null)) { trackedBarsBySlot[playerId] = null; RemoveActiveBar(playerStatusBarView); if ((Object)(object)playerStatusBarView != (Object)null) { ReleaseBarView(playerStatusBarView); } Plugin.LogDebug($"Removed bar playerKey={playerId}."); } } private void ClearBars() { int num = activeBarCount; for (int i = 0; i < trackedBarsBySlot.Length; i++) { PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[i]; if ((Object)(object)playerStatusBarView != (Object)null) { trackedBarsBySlot[i] = null; ReleaseBarView(playerStatusBarView); } } Array.Clear(activeBars, 0, activeBarCount); activeBarCount = 0; if (num > 0) { CadaverGrowthInfectionProvider.InvalidateAll(); Plugin.LogDebug($"Cleared all bars count={num}."); } } private void ReleaseBarsFrom(int firstSlot) { for (int i = firstSlot; i < trackedBarsBySlot.Length; i++) { RemoveBar(i); } } private void OnDestroy() { Plugin.LogDebug($"Runtime manager destroyed tracked={activeBarCount}."); PlayerStatusSnapshotSync.ClearSnapshots(); CadaverGrowthInfectionProvider.InvalidateAll(); DestroyAllBars(); } private void DestroyAllBars() { for (int i = 0; i < trackedBarsBySlot.Length; i++) { PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[i]; if ((Object)(object)playerStatusBarView != (Object)null) { Object.Destroy((Object)(object)((Component)playerStatusBarView).gameObject); } } trackedBarsBySlot = Array.Empty<PlayerStatusBarView>(); activeBars = Array.Empty<PlayerStatusBarView>(); activeBarCount = 0; while (pooledBars.Count > 0) { PlayerStatusBarView playerStatusBarView2 = pooledBars.Pop(); if ((Object)(object)playerStatusBarView2 != (Object)null) { Object.Destroy((Object)(object)((Component)playerStatusBarView2).gameObject); } } } private void OnEnable() { Plugin.LogDebug("Runtime manager enabled."); Plugin.Settings.SettingsChanged += HandleSettingsChanged; } private void OnDisable() { Plugin.LogDebug($"Runtime manager disabled tracked={activeBarCount}."); Plugin.Settings.SettingsChanged -= HandleSettingsChanged; } private void HandleSettingsChanged() { nextRefreshTime = 0f; } private void LogDebugBlocked(string reason) { if (Plugin.Settings.DebugLogging && !(Time.unscaledTime < nextDebugSummaryTime)) { nextDebugSummaryTime = Time.unscaledTime + 5f; Plugin.LogDebug($"Refresh blocked reason={reason} tracked={activeBarCount}."); } } private static void LogDebugPlayerSkip(int slot, PlayerControllerB player, string reason) { if (Plugin.Settings.DebugLogging && !(reason == "local-player")) { Plugin.LogDebug(string.Format("Skipped slot={0} reason={1} clientId={2} name='{3}' controlled={4} dead={5} health={6} active={7}.", slot, reason, ((Object)(object)player != (Object)null) ? player.playerClientId.ToString() : "null", ((Object)(object)player != (Object)null) ? player.playerUsername : "null", (Object)(object)player != (Object)null && player.isPlayerControlled, (Object)(object)player != (Object)null && player.isPlayerDead, ((Object)(object)player != (Object)null) ? player.health : (-1), (Object)(object)player != (Object)null && ((Component)player).gameObject.activeInHierarchy)); } } private void LogDebugSummary(StartOfRound startOfRound, PlayerControllerB localPlayer, int playerSlots, int createdCount, int existingCount, int skippedCount) { if (Plugin.Settings.DebugLogging && !(Time.unscaledTime < nextDebugSummaryTime)) { nextDebugSummaryTime = Time.unscaledTime + 5f; Camera val = StatusBarBillboard.ResolveViewCamera(); Plugin.LogDebug(string.Format("Refresh summary slots={0} tracked={1} created={2} existing={3} skipped={4} localClient={5} inShipPhase={6} shipHasLanded={7} shipDoorsEnabled={8} shipIsLeaving={9} dayStarted={10} camera='{11}'.", playerSlots, activeBarCount, createdCount, existingCount, skippedCount, localPlayer.playerClientId, startOfRound.inShipPhase, startOfRound.shipHasLanded, startOfRound.shipDoorsEnabled, startOfRound.shipIsLeaving, TimeOfDay.Instance?.currentDayTimeStarted, ((Object)(object)val != (Object)null) ? ((Object)val).name : "none")); } } } internal readonly struct PlayerStatusFrameContext { public readonly StatusBarConfig Settings; public readonly PlayerControllerB LocalPlayer; public readonly Camera? ViewCamera; public readonly bool HasBillboardRotation; public readonly Quaternion BillboardRotation; public readonly Vector3 ObserverPosition; public readonly float MaxDistanceSqr; public readonly float AnchorYOffset; public readonly float UiScale; public PlayerStatusFrameContext(StatusBarConfig settings, PlayerControllerB localPlayer, Camera? viewCamera, bool hasBillboardRotation, Quaternion billboardRotation, Vector3 observerPosition) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0028: Unknown result type (might be due to invalid IL or missing references) Settings = settings; LocalPlayer = localPlayer; ViewCamera = viewCamera; HasBillboardRotation = hasBillboardRotation; BillboardRotation = billboardRotation; ObserverPosition = observerPosition; MaxDistanceSqr = settings.MaxDistanceSqr; AnchorYOffset = settings.AnchorYOffset; UiScale = settings.UiScale; } } [BepInPlugin("playerstatusbars", "PlayerStatusBars", "0.3.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private const string RuntimeObjectName = "PlayerStatusBars.Runtime"; private const string LegacyConfigFileName = "Codex.OtherPlayerStatusBars.cfg"; private static GameObject? runtimeObject; private static PlayerStatusBarManager? runtimeManager; internal static ManualLogSource Log { get; private set; } internal static StatusBarConfig Settings { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; MigrateLegacyConfigFile(); RemoveObsoleteConfigKeys(); bool flag = DetectChineseProject(); Settings = StatusBarConfig.Create(((BaseUnityPlugin)this).Config, flag); RegisterLethalConfig(flag); EnsureRuntimeManager(); ((BaseUnityPlugin)this).Config.Save(); Log.LogInfo((object)"PlayerStatusBars loaded."); LogDebug("Debug logging is enabled."); } private void OnDestroy() { LogDebug("Plugin component destroyed; runtime manager remains active."); } internal static void LogDebug(string message) { StatusBarConfig settings = Settings; if (settings != null && settings.DebugLogging) { Log.LogInfo((object)("[Debug] " + message)); } } private void MigrateLegacyConfigFile() { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; string directoryName = Path.GetDirectoryName(configFilePath); if (!string.IsNullOrEmpty(directoryName)) { string text = Path.Combine(directoryName, "Codex.OtherPlayerStatusBars.cfg"); if (File.Exists(text) && !File.Exists(configFilePath)) { File.Copy(text, configFilePath); ((BaseUnityPlugin)this).Config.Reload(); Log.LogInfo((object)("Migrated legacy config from Codex.OtherPlayerStatusBars.cfg to " + Path.GetFileName(configFilePath) + ".")); } } } private void RemoveObsoleteConfigKeys() { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (!File.Exists(configFilePath)) { return; } try { if (LegacyConfigCleaner.TryRemoveObsoleteKeys(File.ReadAllLines(configFilePath), out string[] cleanedLines)) { File.WriteAllLines(configFilePath, cleanedLines); ((BaseUnityPlugin)this).Config.Reload(); Log.LogInfo((object)"Removed obsolete settings from the config file."); } } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { Log.LogWarning((object)("Could not remove obsolete settings: " + ex.Message)); } } private static void RegisterLethalConfig(bool useChinese) { if (!Chainloader.PluginInfos.ContainsKey("ainavt.lc.lethalconfig")) { return; } try { LethalConfigIntegration.Register(Settings, useChinese); } catch (Exception ex) { Log.LogWarning((object)("LethalConfig registration failed: " + ex.Message)); } } private static void EnsureRuntimeManager() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown if ((Object)(object)runtimeManager != (Object)null) { GameObject gameObject = ((Component)runtimeManager).gameObject; if ((Object)(object)gameObject != (Object)null && !gameObject.activeSelf) { gameObject.SetActive(true); } LogDebug("Runtime manager already active."); return; } if ((Object)(object)runtimeObject == (Object)null) { runtimeObject = new GameObject("PlayerStatusBars.Runtime"); ((Object)runtimeObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)runtimeObject); } else if (!runtimeObject.activeSelf) { runtimeObject.SetActive(true); } runtimeManager = runtimeObject.GetComponent<PlayerStatusBarManager>(); if ((Object)(object)runtimeManager == (Object)null) { runtimeManager = runtimeObject.AddComponent<PlayerStatusBarManager>(); } LogDebug("Runtime manager created."); } private static bool DetectChineseProject() { foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if (ChineseProjectDetection.Matches(value.Metadata.GUID, value.Location)) { return true; } } return HasChineseProjectFiles(); } private static bool HasChineseProjectFiles() { try { string path = Path.Combine(Paths.PluginPath, "Aueser-LC_Chinese_Project"); if (Directory.Exists(path) && Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Length != 0) { return true; } return Directory.GetFiles(Paths.PluginPath, "V81TestChn.dll", SearchOption.AllDirectories).Length != 0; } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { Log.LogWarning((object)("Could not check for LC Chinese Project: " + ex.Message)); return false; } } } internal sealed class BarPositionStabilizer { private const float DeadZoneMeters = 0.04f; private bool hasPosition; public float X { get; private set; } public float Y { get; private set; } public float Z { get; private set; } public void Update(float targetX, float targetY, float targetZ) { if (!hasPosition) { hasPosition = true; X = targetX; Y = targetY; Z = targetZ; return; } float num = targetX - X; float num2 = targetY - Y; float num3 = targetZ - Z; float num4 = (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3); if (!(num4 <= 0.04f)) { float num5 = (num4 - 0.04f) / num4; X += num * num5; Y += num2 * num5; Z += num3 * num5; } } public void Reset() { hasPosition = false; } } internal sealed class BarValueAnimation { private const float TrailHoldSeconds = 0.4f; private const float TrailPointsPerSecond = 66f; private const float RisePointsPerSecond = 54f; private bool hasValue; private float lastUpdateTime; private float trailHoldEndTime; public float Fill { get; private set; } public float Trail { get; private set; } public int Target { get; private set; } public void Update(int targetValue, float now) { if (!hasValue) { hasValue = true; Fill = targetValue; Trail = targetValue; Target = targetValue; lastUpdateTime = now; return; } float num = Math.Max(0f, now - lastUpdateTime); lastUpdateTime = now; Target = targetValue; if ((float)targetValue < Fill) { Trail = Math.Max(Trail, Fill); Fill = targetValue; trailHoldEndTime = now + 0.4f; } else if (Fill < (float)targetValue) { Fill = Math.Min(targetValue, Fill + 54f * num); } if (now >= trailHoldEndTime) { Trail -= 66f * num; } Trail = Math.Max(Trail, Fill); } public void Reset() { hasValue = false; Fill = 0f; Trail = 0f; Target = 0; lastUpdateTime = 0f; trailHoldEndTime = 0f; } } internal sealed class HealthDisplayModel { public const int LowHealthRegenerationTarget = 20; private const int CriticalHealthThreshold = 10; private const int MaxCriticalHealth = 9; private const int LethalHitProtectionDamage = 50; private const int LethalHitProtectedHealth = 5; private const float LowHealthRegenerationInterval = 1f; private const float DisplayStepInterval = 0.05f; private const float DisplayMaxDuration = 0.4f; private static readonly (int Damage, int Weight)[] CommonHits = new(int, int)[14] { (7, 1), (8, 1), (10, 7), (20, 4), (25, 1), (30, 7), (35, 1), (40, 3), (50, 3), (70, 1), (80, 1), (85, 1), (90, 2), (100, 1) }; private bool hasObservedRawHealth; private int lastObservedRawHealth; private bool hasObservedDamageTimestamp; private float lastObservedDamageTimestamp; private bool hasObservedCriticalState; private bool lastObservedCriticalState; private int predictionStartHealth; private int predictionUpperStartHealth; private float predictionStartTime; private bool hasDisplayedHealth; private int animationTarget; private int animationStartValue; private float animationStartTime; private float animationDuration; public int TargetHealth { get; private set; } public int DisplayedHealth { get; private set; } public bool IsPredictionActive { get; private set; } public bool IsHoldingRecoveredHealth { get; private set; } public bool DamageSignalChanged { get; private set; } public bool CriticalSignalChanged { get; private set; } public bool IsLowHealth { get { if (TargetHealth >= 20) { return DisplayedHealth < 20; } return true; } } public HealthDisplayModel() { Reset(); } public static int EstimateHealthAfterCriticalHit(int healthBeforeHit) { int num = 0; int num2 = 0; (int, int)[] commonHits = CommonHits; for (int i = 0; i < commonHits.Length; i++) { (int, int) tuple = commonHits[i]; int item = tuple.Item1; int item2 = tuple.Item2; int num3 = ((healthBeforeHit - item <= 0 && item < 50) ? 5 : (healthBeforeHit - item)); if (num3 >= 1 && num3 <= 9) { num += num3 * item2; num2 += item2; } } if (num2 != 0) { return (int)Math.Round((double)num / (double)num2); } return 5; } public int Update(int rawHealth, bool isExact, bool isCritical, float damageTimestamp, float now) { bool isFirstCriticalObservation = !hasObservedCriticalState && isCritical; DamageSignalChanged = ObserveDamageTimestamp(damageTimestamp); CriticalSignalChanged = ObserveCriticalState(isCritical); TargetHealth = ResolveTarget(rawHealth, isExact, isCritical, isFirstCriticalObservation, now); DisplayedHealth = ResolveAnimatedDisplay(TargetHealth, now); return DisplayedHealth; } public void Reset() { hasObservedRawHealth = false; lastObservedRawHealth = int.MinValue; hasObservedDamageTimestamp = false; lastObservedDamageTimestamp = 0f; hasObservedCriticalState = false; lastObservedCriticalState = false; DamageSignalChanged = false; CriticalSignalChanged = false; ResetPrediction(); IsHoldingRecoveredHealth = false; ResetDisplay(); } public void ResetDisplay() { hasDisplayedHealth = false; TargetHealth = int.MinValue; DisplayedHealth = int.MinValue; animationTarget = int.MinValue; animationStartValue = 0; animationStartTime = 0f; animationDuration = 0f; } private bool ObserveDamageTimestamp(float damageTimestamp) { if (!hasObservedDamageTimestamp) { hasObservedDamageTimestamp = true; lastObservedDamageTimestamp = damageTimestamp; return false; } if (damageTimestamp == lastObservedDamageTimestamp) { return false; } lastObservedDamageTimestamp = damageTimestamp; return true; } private bool ObserveCriticalState(bool isCritical) { if (!hasObservedCriticalState) { hasObservedCriticalState = true; lastObservedCriticalState = isCritical; return false; } if (isCritical == lastObservedCriticalState) { return false; } lastObservedCriticalState = isCritical; return true; } private int ResolveTarget(int rawHealth, bool isExact, bool isCritical, bool isFirstCriticalObservation, float now) { bool flag = !hasObservedRawHealth || rawHealth != lastObservedRawHealth; hasObservedRawHealth = true; lastObservedRawHealth = rawHealth; if (isExact || rawHealth <= 0) { ResetPrediction(); IsHoldingRecoveredHealth = false; return rawHealth; } bool num = CriticalSignalChanged && isCritical; bool flag2 = CriticalSignalChanged && !isCritical; if (num || (isFirstCriticalObservation && rawHealth > 9)) { BeginCriticalEstimate(flag ? rawHealth : ResolveCurrentEstimate(rawHealth, now), now); } else if (flag || DamageSignalChanged) { ResetPrediction(); IsHoldingRecoveredHealth = false; } else if (flag2) { IsHoldingRecoveredHealth = HasHadTimeToRecover(now); ResetPrediction(); } if (IsHoldingRecoveredHealth) { return 20; } if (!IsPredictionActive) { if (rawHealth >= 20) { return rawHealth; } BeginPrediction(rawHealth, rawHealth, now); } int num2 = PredictHealth(predictionStartHealth, now); if (isCritical) { return Math.Min(num2, 19); } if (num2 >= 20) { ResetPrediction(); IsHoldingRecoveredHealth = true; return 20; } return num2; } private int ResolveCurrentEstimate(int rawHealth, float now) { if (IsHoldingRecoveredHealth) { return 20; } if (!IsPredictionActive) { return rawHealth; } return PredictHealth(predictionStartHealth, now); } private void BeginCriticalEstimate(int healthBeforeHit, float now) { IsHoldingRecoveredHealth = false; BeginPrediction(EstimateHealthAfterCriticalHit(healthBeforeHit), 9, now); } private bool HasHadTimeToRecover(float now) { if (IsPredictionActive) { return PredictHealth(predictionUpperStartHealth, now) >= 19; } return false; } private int PredictHealth(int startHealth, float now) { int num = Math.Max(0, (int)Math.Floor((now - predictionStartTime) / 1f)); return Math.Min(20, startHealth + num); } private void BeginPrediction(int startHealth, int upperStartHealth, float now) { IsPredictionActive = true; predictionStartHealth = startHealth; predictionUpperStartHealth = upperStartHealth; predictionStartTime = now; } private void ResetPrediction() { IsPredictionActive = false; predictionStartHealth = int.MinValue; predictionUpperStartHealth = int.MinValue; predictionStartTime = 0f; } private int ResolveAnimatedDisplay(int targetHealth, float now) { if (!hasDisplayedHealth) { hasDisplayedHealth = true; animationTarget = targetHealth; return targetHealth; } if (targetHealth == DisplayedHealth) { animationTarget = targetHealth; return targetHealth; } if (targetHealth != animationTarget) { animationTarget = targetHealth; animationStartValue = DisplayedHealth; animationStartTime = now; animationDuration = Math.Min(0.4f, (float)Math.Abs(targetHealth - DisplayedHealth) * 0.05f); } float num = Math.Min(1f, Math.Max(0f, (now - animationStartTime) / animationDuration)); return (int)Math.Round((float)animationStartValue + (float)(targetHealth - animationStartValue) * num); } } internal sealed class InfectionDisplayModel { private const int MinimumPercent = 1; private const int MaximumPercent = 99; private const float ExactPredictionWindow = 0.5f; private const float MaxSmoothedPredictionWindow = 60f; private const float DisplayStepInterval = 0.1f; private bool isPredictionActive; private float lastObservedMeter; private float lastObservedSampleTime; private float predictionStartMeter; private float predictionStartTime; private float predictionRate; private float predictionWindow; private bool hasDisplayedPercent; private int displayedPercent; private float nextDisplayStepTime; public int Update(float rawMeter, bool isExact, float now) { rawMeter = Clamp01(rawMeter); if (!isPredictionActive) { BeginPrediction(rawMeter, 0f, 0.5f, now); lastObservedMeter = rawMeter; lastObservedSampleTime = now; return ResolveSteppedPercent(ToPercent(rawMeter), canDrop: true, now); } bool flag = rawMeter < lastObservedMeter; if (flag) { BeginPrediction(rawMeter, 0f, 0.5f, now); lastObservedSampleTime = now; } else if (rawMeter > lastObservedMeter) { float num = Math.Max(0.5f, now - lastObservedSampleTime); float window = (isExact ? 0.5f : Math.Min(60f, num)); BeginPrediction(rawMeter, (rawMeter - lastObservedMeter) / num, window, now); lastObservedSampleTime = now; } lastObservedMeter = rawMeter; return ResolveSteppedPercent(ToPercent(PredictMeter(now)), isExact || flag, now); } public void Reset() { isPredictionActive = false; lastObservedMeter = 0f; lastObservedSampleTime = 0f; predictionStartMeter = 0f; predictionStartTime = 0f; predictionRate = 0f; predictionWindow = 0f; hasDisplayedPercent = false; displayedPercent = int.MinValue; nextDisplayStepTime = 0f; } private void BeginPrediction(float rawMeter, float rate, float window, float now) { isPredictionActive = true; predictionStartMeter = rawMeter; predictionStartTime = now; predictionRate = rate; predictionWindow = window; } private float PredictMeter(float now) { float num = Math.Min(now - predictionStartTime, predictionWindow); return Clamp01(predictionStartMeter + predictionRate * num); } private int ResolveSteppedPercent(int targetPercent, bool canDrop, float now) { if (!hasDisplayedPercent) { hasDisplayedPercent = true; displayedPercent = targetPercent; nextDisplayStepTime = now + 0.1f; return displayedPercent; } if (targetPercent < displayedPercent) { if (canDrop) { displayedPercent = targetPercent; nextDisplayStepTime = now + 0.1f; } return displayedPercent; } if (now < nextDisplayStepTime) { return displayedPercent; } displayedPercent = Math.Min(targetPercent, displayedPercent + 1); nextDisplayStepTime = now + 0.1f; return displayedPercent; } private static int ToPercent(float meter) { int val = (int)Math.Floor(Clamp01(meter) * 100f); return Math.Min(99, Math.Max(1, val)); } private static float Clamp01(float value) { return Math.Min(1f, Math.Max(0f, value)); } } internal sealed class PlayerStatusBarStrip : MonoBehaviour { internal enum StripType { Health, Infection } private const float Width = 172f; private const float Height = 20f; private bool visible = true; private float currentValue; private float maxValue = 1f; private bool renderAsPercent; private StripType stripType; private RectTransform rectTransform; private RectTransform fillRect; private Image backgroundImage; private Image fillImage; private TextMeshProUGUI text; private Outline border; private bool dirty = true; private bool lastAppliedVisible = true; private float lastAppliedNormalized = -1f; private string lastAppliedText = string.Empty; private bool lastAppliedTextEnabled = true; private Color lastAppliedBackgroundColor = Color.clear; private Color lastAppliedFillColor = Color.clear; private Color lastAppliedBorderColor = Color.clear; private int lastAppliedSettingsRevision = -1; private bool hasFillColorOverride; private Color fillColorOverride; public void Initialize(StripType type) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) stripType = type; rectTransform = ((Component)this).gameObject.GetComponent<RectTransform>(); if ((Object)(object)rectTransform == (Object)null) { rectTransform = ((Component)this).gameObject.AddComponent<RectTransform>(); } rectTransform.sizeDelta = new Vector2(172f, 20f); rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); GameObject val = new GameObject("Background", new Type[3] { typeof(RectTransform), typeof(Image), typeof(Outline) }); val.transform.SetParent(((Component)this).transform, false); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; backgroundImage = val.GetComponent<Image>(); ((Graphic)backgroundImage).raycastTarget = false; border = val.GetComponent<Outline>(); GameObject val2 = new GameObject("Fill", new Type[2] { typeof(RectTransform), typeof(Image) }); val2.transform.SetParent(val.transform, false); fillRect = val2.GetComponent<RectTransform>(); fillRect.anchorMin = Vector2.zero; fillRect.anchorMax = Vector2.one; fillRect.pivot = new Vector2(0f, 0.5f); fillRect.offsetMin = new Vector2(1f, 1f); fillRect.offsetMax = new Vector2(-1f, -1f); ((Transform)fillRect).localScale = Vector3.one; fillImage = val2.GetComponent<Image>(); ((Graphic)fillImage).raycastTarget = false; fillImage.type = (Type)0; GameObject val3 = new GameObject("Text", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val3.transform.SetParent(val.transform, false); RectTransform component2 = val3.GetComponent<RectTransform>(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = new Vector2(6f, 1f); component2.offsetMax = new Vector2(-6f, -1f); text = val3.GetComponent<TextMeshProUGUI>(); ((TMP_Text)text).alignment = (TextAlignmentOptions)514; ((TMP_Text)text).fontSize = 12f; ((TMP_Text)text).enableWordWrapping = false; ((Graphic)text).color = Color.white; ((Graphic)text).raycastTarget = false; } public void SetDisplay(float value, float max, bool showPercent) { float num = Mathf.Clamp(value, 0f, (max <= 0f) ? 1f : max); float num2 = Mathf.Max(max, 0.0001f); if (Mathf.Approximately(currentValue, num) && Mathf.Approximately(maxValue, num2) && renderAsPercent == showPercent) { visible = true; return; } currentValue = num; maxValue = num2; renderAsPercent = showPercent; visible = true; dirty = true; } public void SetVisible(bool isVisible) { if (visible != isVisible || ((Component)this).gameObject.activeSelf != isVisible) { visible = isVisible; ((Component)this).gameObject.SetActive(isVisible); dirty = true; } } public void SetFillColorOverride(bool enabled, Color color) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (hasFillColorOverride != enabled || (enabled && !(fillColorOverride == color))) { hasFillColorOverride = enabled; fillColorOverride = color; dirty = true; } } public void ApplyIfDirty(StatusBarConfig settings) { //IL_006f: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017f: 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_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)backgroundImage == (Object)null || (Object)(object)fillImage == (Object)null || (Object)(object)this.text == (Object)null || !visible || (!dirty && settings.Revision == lastAppliedSettingsRevision)) { return; } float num = Mathf.Clamp01(currentValue / maxValue); Color val = ((stripType == StripType.Health) ? settings.GetHealthFillColor() : settings.GetInfectionFillColor()); if (hasFillColorOverride) { val = fillColorOverride; } Color backgroundColor = settings.GetBackgroundColor(); Color borderColor = settings.GetBorderColor(); bool showText = settings.ShowText; string text = string.Empty; if (showText) { text = (renderAsPercent ? StatusBarTextCache.GetInfectionLabel(Mathf.RoundToInt(num * 100f)) : StatusBarTextCache.GetHealthLabel(Mathf.RoundToInt(currentValue), Mathf.RoundToInt(maxValue))); } bool flag = backgroundColor != lastAppliedBackgroundColor || val != lastAppliedFillColor || borderColor != lastAppliedBorderColor; bool flag2 = !Mathf.Approximately(num, lastAppliedNormalized); bool flag3 = showText != lastAppliedTextEnabled; bool flag4 = text != lastAppliedText; bool flag5 = visible != lastAppliedVisible; if (dirty || flag || flag2 || flag3 || flag4 || flag5) { if (flag) { ((Graphic)backgroundImage).color = backgroundColor; ((Shadow)border).effectColor = borderColor; ((Shadow)border).effectDistance = new Vector2(1f, -1f); ((Graphic)fillImage).color = val; lastAppliedBackgroundColor = backgroundColor; lastAppliedFillColor = val; lastAppliedBorderColor = borderColor; } if (dirty || flag2) { ((Transform)fillRect).localScale = new Vector3(num, 1f, 1f); lastAppliedNormalized = num; } if (dirty || flag3) { ((Behaviour)this.text).enabled = showText; lastAppliedTextEnabled = showText; } if (showText && (dirty || flag4)) { ((TMP_Text)this.text).text = text; lastAppliedText = text; } lastAppliedVisible = visible; lastAppliedSettingsRevision = settings.Revision; dirty = false; } } } internal sealed class PlayerStatusBarView : MonoBehaviour { private static readonly Color CriticalHealthBarColor = new Color(0.88f, 0.15f, 0.12f, 0.95f); private const float InfectionStripYOffset = 25f; private const float SegmentedStripGap = 1.25f; private const float SegmentedPanelPadding = 1.67f; private const float SegmentedPanelBrightness = 1.25f; private const float DebugVisibilityLogInterval = 5f; private readonly HealthDisplayModel healthDisplay = new HealthDisplayModel(); private readonly InfectionDisplayModel infectionDisplay = new InfectionDisplayModel(); private readonly BarValueAnimation healthBarAnimation = new BarValueAnimation(); private readonly BarValueAnimation infectionBarAnimation = new BarValueAnimation(); private readonly BarPositionStabilizer positionStabilizer = new BarPositionStabilizer(); private PlayerControllerB targetPlayer; private Transform? anchor; private ulong boundActualClientId = ulong.MaxValue; private int boundPlayerKey = -1; private PlayerStatusBarStrip healthStrip; private PlayerStatusBarStrip infectionStrip; private StatusBarConfig.BarStyle appliedBarStyle; private SegmentedBarStrip? segmentedHealthStrip; private SegmentedBarStrip? segmentedInfectionStrip; private Image? segmentedPanel; private bool isSegmentedPanelLaidOut; private bool lastSegmentedPanelShowedInfection; private int lastSegmentedPanelSettingsRevision = -1; private Canvas worldCanvas; private RectTransform canvasRect; private int lastDisplayedHealth = int.MinValue; private int lastDisplayedInfectionPercent = int.MinValue; private bool wasHealthVisible; private bool wasInfectionVisible; private bool lastCanvasEnabled = true; private Camera? lastAppliedCanvasCamera; private float lastAppliedUiScale = -1f; private bool wasTargetDead; private bool wasUsingSnapshot; private string lastDebugVisibilityState = string.Empty; private float nextDebugVisibilityLogTime; private int lastDebugRawHealth = int.MinValue; private bool lastDebugRawHealthExact; private bool lastDebugRawCriticalState; public int PlayerId { get { if (boundPlayerKey < 0) { if (!((Object)(object)targetPlayer != (Object)null)) { return -1; } return (int)targetPlayer.playerClientId; } return boundPlayerKey; } } private bool IsSegmented => appliedBarStyle == StatusBarConfig.BarStyle.Segmented; public bool Initialize(PlayerControllerB player, int playerKey) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) worldCanvas = ((Component)this).gameObject.AddComponent<Canvas>(); worldCanvas.renderMode = (RenderMode)2; worldCanvas.overrideSorting = true; worldCanvas.sortingOrder = 50; canvasRect = ((Component)worldCanvas).GetComponent<RectTransform>(); canvasRect.sizeDelta = new Vector2(220f, 84f); healthStrip = CreateStrip("HealthStrip", PlayerStatusBarStrip.StripType.Health, 0f); infectionStrip = CreateStrip("InfectionStrip", PlayerStatusBarStrip.StripType.Infection, 25f); Bind(player, playerKey); return true; } internal void Bind(PlayerControllerB player, int playerKey) { targetPlayer = player; boundPlayerKey = playerKey; boundActualClientId = player.actualClientId; anchor = ResolveAnchor(player); ResetRuntimeState(); ((Object)this).name = $"PlayerStatusBar_{player.playerClientId}"; ((Component)this).gameObject.SetActive(true); } internal void Release() { if ((Object)(object)worldCanvas != (Object)null) { SetCanvasEnabled(enabled: false); } if ((Object)(object)healthStrip != (Object)null && (Object)(object)infectionStrip != (Object)null) { HideStrips(); } targetPlayer = null; anchor = null; boundPlayerKey = -1; boundActualClientId = ulong.MaxValue; ResetRuntimeState(); ((Component)this).gameObject.SetActive(false); } public bool IsStillValid(PlayerControllerB localPlayer) { if ((Object)(object)targetPlayer == (Object)null || (Object)(object)localPlayer == (Object)null) { return false; } if ((Object)(object)targetPlayer == (Object)(object)localPlayer) { return false; } if (!targetPlayer.disconnectedMidGame) { return ((Component)targetPlayer).gameObject.activeInHierarchy; } return false; } public bool MatchesPlayer(PlayerControllerB player, PlayerControllerB localPlayer, int playerKey) { if (IsStillValid(localPlayer) && (Object)(object)targetPlayer == (Object)(object)player && boundPlayerKey == playerKey) { return boundActualClientId == player.actualClientId; } return false; } internal void Tick(in PlayerStatusFrameContext context) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)targetPlayer == (Object)null) { return; } StatusBarConfig settings = context.Settings; ApplyBarStyle(settings.Style); PlayerStatusSnapshot snapshot; bool flag = PlayerStatusSnapshotSync.TryGetSnapshot(targetPlayer, out snapshot); HandleSnapshotSourceTransition(flag); bool targetIsDead = ((!flag) ? (targetPlayer.isPlayerDead || PlayerStatusSnapshotSync.SlotInheritance.ResolveHealth(boundPlayerKey, targetPlayer.health) <= 0) : (snapshot.IsDead || snapshot.Health <= 0)); HandleDeathStateTransition(targetIsDead); if (!IsTargetReadyForDisplay(targetIsDead)) { HideAll(settings, context.ViewCamera); return; } float unscaledTime = Time.unscaledTime; OwnerStatusReport report; bool hasOwnerReport = PlayerStatusSnapshotSync.TryGetOwnerReport(targetPlayer, boundPlayerKey, out report); int displayHealth = ResolveDisplayHealth(flag, in snapshot, hasOwnerReport, in report, unscaledTime); if (anchor == null) { anchor = ResolveAnchor(targetPlayer); } Vector3 val = (((Object)(object)anchor != (Object)null) ? anchor.position : ((Component)targetPlayer).transform.position); ((Component)this).transform.position = ResolveStablePosition(val + Vector3.up * context.AnchorYOffset, context.ViewCamera); if (context.HasBillboardRotation) { ((Component)this).transform.rotation = context.BillboardRotation; } ApplyCanvasCamera(context.ViewCamera); ApplyCanvasScale(context.UiScale); if (!ShouldShowForDistance(in context, out var distanceSqr)) { HideForDistance(context.ViewCamera, distanceSqr); return; } UpdateHealthStrip(displayHealth, unscaledTime); LogDebugVisibility("visible", context.ViewCamera, distanceSqr); bool showInfection = UpdateInfectionStrip(flag, in snapshot, hasOwnerReport, in report, unscaledTime); UpdateSegmentedPanel(settings, showInfection); SetCanvasEnabled(enabled: true); ApplyStripsIfDirty(settings); wasHealthVisible = true; wasInfectionVisible = showInfection; } private Vector3 ResolveStablePosition(Vector3 targetPosition, Camera? viewCamera) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_0052: 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) //IL_0058: 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_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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) positionStabilizer.Update(targetPosition.x, targetPosition.y, targetPosition.z); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(positionStabilizer.X, positionStabilizer.Y, positionStabilizer.Z); if ((Object)(object)viewCamera == (Object)null) { return val; } Vector3 val2 = viewCamera.WorldToScreenPoint(val); if (val2.z <= 0f) { return val; } val2.x = Mathf.Round(val2.x); val2.y = Mathf.Round(val2.y); return viewCamera.ScreenToWorldPoint(val2); } private int ResolveDisplayHealth(bool hasSyncedSnapshot, in PlayerStatusSnapshot syncedSnapshot, bool hasOwnerReport, in OwnerStatusReport ownerReport, float now) { bool isExact; bool isCritical; int num; if (hasSyncedSnapshot) { num = syncedSnapshot.Health; isExact = syncedSnapshot.IsHealthExact; isCritical = syncedSnapshot.IsCritical; } else { num = (hasOwnerReport ? ownerReport.Health : PlayerStatusSnapshotSync.SlotInheritance.ResolveHealth(boundPlayerKey, targetPlayer.health)); isExact = hasOwnerReport; isCritical = targetPlayer.criticallyInjured; } num = (lastDebugRawHealth = Mathf.Clamp(num, 0, 100)); lastDebugRawHealthExact = isExact; lastDebugRawCriticalState = isCritical; return healthDisplay.Update(num, isExact, isCritical, targetPlayer.timeSinceTakingDamage, now); } private void UpdateHealthStrip(int displayHealth, float now) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (IsSegmented && (Object)(object)segmentedHealthStrip != (Object)null) { healthBarAnimation.Update(healthDisplay.TargetHealth, now); string numberLabel = StatusBarTextCache.GetNumberLabel(Mathf.RoundToInt(healthBarAnimation.Fill)); segmentedHealthStrip.SetFillColorOverride(healthDisplay.IsLowHealth, StatusBarConfig.SegmentedCriticalColor); segmentedHealthStrip.SetValues(healthBarAnimation.Fill, healthBarAnimation.Trail, healthBarAnimation.Target, numberLabel); segmentedHealthStrip.SetVisible(isVisible: true); return; } healthStrip.SetFillColorOverride(healthDisplay.IsLowHealth, CriticalHealthBarColor); if (!wasHealthVisible || displayHealth != lastDisplayedHealth) { healthStrip.SetDisplay(displayHealth, 100f, showPercent: false); lastDisplayedHealth = displayHealth; } healthStrip.SetVisible(isVisible: true); } private bool UpdateInfectionStrip(bool hasSyncedSnapshot, in PlayerStatusSnapshot syncedSnapshot, bool hasOwnerReport, in OwnerStatusReport ownerReport, float now) { if (!PlayerStatusSnapshotSync.IsInfectionDisplayAllowed) { HideInfectionStrip(); return false; } bool flag; bool isExact; float infectionMeter; if (hasSyncedSnapshot && syncedSnapshot.IsInfectionKnown) { flag = syncedSnapshot.HasInfection; isExact = syncedSnapshot.IsInfectionExact; infectionMeter = syncedSnapshot.InfectionMeter; } else if (hasOwnerReport && ownerReport.IsInfectionKnown) { flag = ownerReport.HasInfection; isExact = true; infectionMeter = ownerReport.InfectionMeter; } else { flag = CadaverGrowthInfectionProvider.TryGetNormalizedInfection(targetPlayer, boundPlayerKey, out infectionMeter); isExact = false; } if (!flag) { if (TryShowInfectionClearing(now)) { return true; } HideInfectionStrip(); return false; } int num = infectionDisplay.Update(infectionMeter, isExact, now); if (IsSegmented && (Object)(object)segmentedInfectionStrip != (Object)null) { infectionBarAnimation.Update(num, now); float fill = infectionBarAnimation.Fill; segmentedInfectionStrip.SetValues(fill, infectionBarAnimation.Trail, fill, StatusBarTextCache.GetPercentLabel(Mathf.RoundToInt(fill))); segmentedInfectionStrip.SetVisible(isVisible: true); return true; } if (!wasInfectionVisible || num != lastDisplayedInfectionPercent) { infectionStrip.SetDisplay(num, 100f, showPercent: true); lastDisplayedInfectionPercent = num; } infectionStrip.SetVisible(isVisible: true); return true; } private bool TryShowInfectionClearing(float now) { if (!IsSegmented || (Object)(object)segmentedInfectionStrip == (Object)null || !wasInfectionVisible) { return false; } infectionBarAnimation.Update(0, now); if (infectionBarAnimation.Trail <= 0f) { return false; } segmentedInfectionStrip.SetValues(0f, infectionBarAnimation.Trail, 0f, StatusBarTextCache.GetPercentLabel(0)); return true; } private void HideInfectionStrip() { infectionStrip.SetVisible(isVisible: false); segmentedInfectionStrip?.SetVisible(isVisible: false); ResetInfectionDisplay(); } private void HideForDistance(Camera? viewCamera, float distanceSqr) { LogDebugVisibility("distance-hidden", viewCamera, distanceSqr); SetCanvasEnabled(enabled: false); HideStrips(); if (wasHealthVisible) { healthDisplay.ResetDisplay(); healthBarAnimation.Reset(); } if (wasInfectionVisible) { ResetInfectionDisplay(); } wasHealthVisible = false; wasInfectionVisible = false; } private void HideAll(StatusBarConfig settings, Camera? viewCamera) { LogDebugVisibility("target-not-ready", viewCamera, -1f); SetCanvasEnabled(enabled: false); HideStrips(); wasHealthVisible = false; wasInfectionVisible = false; ResetInfectionDisplay(); ApplyStripsIfDirty(settings); } private void HideStrips() { healthStrip.SetVisible(isVisible: false); infectionStrip.SetVisible(isVisible: false); segmentedHealthStrip?.SetVisible(isVisible: false); segmentedInfectionStrip?.SetVisible(isVisible: false); } private void ApplyBarStyle(StatusBarConfig.BarStyle style) { if (style != appliedBarStyle) { appliedBarStyle = style; if (IsSegmented && (Object)(object)segmentedPanel == (Object)null) { CreateSegmentedBars(); } HideStrips(); Image? obj = segmentedPanel; if (obj != null) { ((Component)obj).gameObject.SetActive(IsSegmented); } wasHealthVisible = false; wasInfectionVisible = false; lastDisplayedHealth = int.MinValue; lastDisplayedInfectionPercent = int.MinValue; healthBarAnimation.Reset(); infectionBarAnimation.Reset(); } } private void CreateSegmentedBars() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown GameObject val = new GameObject("SegmentedPanel", new Type[2] { typeof(RectTransform), typeof(Image) }); val.transform.SetParent(((Component)worldCanvas).transform, false); val.transform.SetAsFirstSibling(); segmentedPanel = val.GetComponent<Image>(); ((Graphic)segmentedPanel).raycastTarget = false; segmentedHealthStrip = CreateSegmentedStrip("SegmentedHealthStrip", "HP", isInfection: false, 0f); segmentedInfectionStrip = CreateSegmentedStrip("SegmentedInfectionStrip", "INF", isInfection: true, 14.25f); } private SegmentedBarStrip CreateSegmentedStrip(string stripName, string label, bool isInfection, float yOffset) { //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_004a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(stripName, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((Component)worldCanvas).transform, false); SegmentedBarStrip segmentedBarStrip = val.AddComponent<SegmentedBarStrip>(); segmentedBarStrip.Initialize(label, isInfection); ((Component)segmentedBarStrip).GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, yOffset); return segmentedBarStrip; } private void UpdateSegmentedPanel(StatusBarConfig settings, bool showInfection) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) if (IsSegmented && !((Object)(object)segmentedPanel == (Object)null)) { if (settings.Revision != lastSegmentedPanelSettingsRevision) { lastSegmentedPanelSettingsRevision = settings.Revision; Color color = settings.GetBackgroundColor() * 1.25f; color.a = 1f; ((Graphic)segmentedPanel).color = color; } if (!isSegmentedPanelLaidOut || showInfection != lastSegmentedPanelShowedInfection) { isSegmentedPanelLaidOut = true; lastSegmentedPanelShowedInfection = showInfection; float num = (showInfection ? 14.25f : 0f); RectTransform rectTransform = ((Graphic)segmentedPanel).rectTransform; rectTransform.sizeDelta = new Vector2(175.34f, 13f + num + 3.34f); rectTransform.anchoredPosition = new Vector2(0f, num * 0.5f); } } } private bool IsTargetReadyForDisplay(bool targetIsDead) { if ((Object)(object)targetPlayer != (Object)null && !targetPlayer.disconnectedMidGame && !targetIsDead) { return ((Component)targetPlayer).gameObject.activeInHierarchy; } return false; } private void ResetRuntimeState() { wasHealthVisible = false; wasInfectionVisible = false; wasTargetDead = false; wasUsingSnapshot = false; lastAppliedCanvasCamera = null; lastAppliedUiScale = -1f; lastDebugVisibilityState = string.Empty; nextDebugVisibilityLogTime = 0f; lastDebugRawHealth = int.MinValue; lastDebugRawHealthExact = false; lastDebugRawCriticalState = false; positionStabilizer.Reset(); ResetStatusDisplay(); } private void ResetStatusDisplay() { lastDisplayedHealth = int.MinValue; healthDisplay.Reset(); healthBarAnimation.Reset(); ResetInfectionDisplay(); } private void ResetInfectionDisplay() { lastDisplayedInfectionPercent = int.MinValue; infectionDisplay.Reset(); infectionBarAnimation.Reset(); } private void HandleSnapshotSourceTransition(bool hasSyncedSnapshot) { if (wasUsingSnapshot != hasSyncedSnapshot) { wasUsingSnapshot = hasSyncedSnapshot; ResetStatusDisplay(); } } private void HandleDeathStateTransition(bool targetIsDead) { if (wasTargetDead != targetIsDead) { wasTargetDead = targetIsDead; ResetStatusDisplay(); } } private void ApplyCanvasScale(float uiScale) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!Mathf.Approximately(lastAppliedUiScale, uiScale)) { ((Transform)canvasRect).localScale = Vector3.one * uiScale; lastAppliedUiScale = uiScale; } } private void ApplyStripsIfDirty(StatusBarConfig settings) { healthStrip.ApplyIfDirty(settings); infectionStrip.ApplyIfDirty(settings); segmentedHealthStrip?.ApplyIfDirty(settings); segmentedInfectionStrip?.ApplyIfDirty(settings); } private void SetCanvasEnabled(bool enabled) { if (lastCanvasEnabled != enabled || ((Behaviour)worldCanvas).enabled != enabled) { ((Behaviour)worldCanvas).enabled = enabled; lastCanvasEnabled = enabled; } } private void ApplyCanvasCamera(Camera? viewCamera) { if (!((Object)(object)lastAppliedCanvasCamera == (Object)(object)viewCamera) || !((Object)(object)worldCanvas.worldCamera == (Object)(object)viewCamera)) { worldCanvas.worldCamera = viewCamera; lastAppliedCanvasCamera = viewCamera; } } private PlayerStatusBarStrip CreateStrip(string stripName, PlayerStatusBarStrip.StripType stripType, float yOffset) { //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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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) GameObject val = new GameObject(stripName, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((Component)worldCanvas).transform, false); val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; PlayerStatusBarStrip playerStatusBarStrip = val.AddComponent<PlayerStatusBarStrip>(); playerStatusBarStrip.Initialize(stripType); ((Component)playerStatusBarStrip).GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, yOffset); return playerStatusBarStrip; } private bool ShouldShowForDistance(in PlayerStatusFrameContext context, out float distanceSqr) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = context.ObserverPosition - ((Component)targetPlayer).transform.position; distanceSqr = ((Vector3)(ref val)).sqrMagnitude; return distanceSqr <= context.MaxDistanceSqr; } private static Transform? ResolveAnchor(PlayerControllerB player) { if ((Object)(object)player.playerGlobalHead != (Object)null) { return player.playerGlobalHead; } if (player.bodyParts != null && player.bodyParts.Length != 0 && (Object)(object)player.bodyParts[0] != (Object)null) { return player.bodyParts[0]; } return ((Component)player).transform; } private void LogDebugVisibility(string state, Camera? viewCamera, float distanceSqr) { //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Settings.DebugLogging && (!(state == lastDebugVisibilityState) || !(Time.unscaledTime < nextDebugVisibilityLogTime))) { lastDebugVisibilityState = state; nextDebugVisibilityLogTime = Time.unscaledTime + 5f; string text = FormatDebugDistance(distanceSqr); Plugin.LogDebug(string.Format("View playerId={0} clientId={1} actualClientId={2} boundActualClientId={3} snapshot={4} name='{5}' state={6} health={7} rawHealth={8} rawHealthExact={9} displayHealthTarget={10} displayHealth={11} rawCritical={12} damageSignalChanged={13} criticalSignalChanged={14} predictionActive={15} naturalHold={16} controlled={17} dead={18} distance={19} canvasEnabled={20} camera='{21}' anchor='{22}' position={23} target={24}.", PlayerId, targetPlayer.playerClientId, targetPlayer.actualClientId, boundActualClientId, wasUsingSnapshot, targetPlayer.playerUsername, state, targetPlayer.health, lastDebugRawHealth, lastDebugRawHealthExact, healthDisplay.TargetHealth, healthDisplay.DisplayedHealth, lastDebugRawCriticalState, healthDisplay.DamageSignalChanged, healthDisplay.CriticalSignalChanged, healthDisplay.IsPredictionActive, healthDisplay.IsHoldingRecoveredHealth, targetPlayer.isPlayerControlled, targetPlayer.isPlayerDead, text, ((Behaviour)worldCanvas).enabled, ((Object)(object)viewCamera != (Object)null) ? ((Object)viewCamera).name : "none", ((Object)(object)anchor != (Object)null) ? ((Object)anchor).name : "none", FormatVector(((Component)this).transform.position), FormatVector(((Component)targetPlayer).transform.position))); } } private static string FormatDebugDistance(float distanceSqr) { if (!(distanceSqr >= 0f)) { return "n/a"; } return $"{Mathf.Sqrt(distanceSqr):0.0}/{Plugin.Settings.MaxDistance:0.0}"; } private static string FormatVector(Vector3 value) { //IL_0005: 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_001b: Unknown result type (might be due to invalid IL or missing references) return $"({value.x:0.0},{value.y:0.0},{value.z:0.0})"; } } internal sealed class SegmentedBarStrip : MonoBehaviour { public const float Width = 172f; public const float Height = 13f; private const int TextureScale = 12; private const int FrameTexels = 5; private const int BevelTexels = 10; private const float FrameThickness = 5f / 12f; private const int CellCount = 10; private const float EndCellWidth = 0.1f; private const float FontSize = 8f; private const string TextShaderName = "TextMeshPro/Distance Field"; private const float TextOutlineWidth = 0.3f; private const float TextOutlineSoftness = 0.12f; private const float LightenBlend = 0.55f; private static readonly Color TrackColor = new Color(1f, 1f, 1f, 0.035f); private static readonly Color HealthTrailColor = new Color(0.85f, 0.83f, 0.77f, 1f); private static readonly Color FrameColor = new Color(0.34f, 0.36f, 0.4f, 1f); private static readonly Color AnimatedBevelTopLeft = new Color(0f, 0f, 0f, 0.6f); private static readonly Color AnimatedBevelBottomRight = new Color(1f, 1f, 1f, 0.06f); private static readonly Color EmptyBevelTopLeft = new Color(1f, 1f, 1f, 0.07f); private static readonly Color EmptyBevelBottomRight = new Color(0f, 0f, 0f, 0.53f); private static readonly Color TextOutlineColor = new Color(0.03f, 0.04f, 0.05f, 1f); private static Texture2D? frameTexture; private static readonly Dictionary<Color, (Texture2D Bevel, Texture2D EndEdge)> filledBevelTextures = new Dictionary<Color, (Texture2D, Texture2D)>(); private static Texture2D? emptyLightEdgesTexture; private static Texture2D? emptyDarkEdgesTexture; private static Texture2D? animatedDarkEdgesTexture; private static Texture2D? animatedLightEdgesTexture; private static Material? outlinedTextMaterial; private static Material? outlinedTextBaseMaterial; private bool usesInfectionColor; private RectTransform fillRect; private RectTransform trailRect; private RectTransform previewRect; private Image fillImage; private Image trailImage; private Image previewImage; private RawImage filledBevel; private RawImage animatedDarkEdges; private RawImage animatedLightEdges; private RawImage fillEndEdge; private TextMeshProUGUI labelText; private TextMeshProUGUI valueText; private float fill = -1f; private float trail = -1f; private float preview = -1f; private string value = string.Empty; private bool hasFillColorOverride; private Color fillColorOverride; private bool dirty = true; private int lastAppliedSettingsRevision