using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; 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: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace OtherPlayerStatusBars { internal static class CadaverGrowthInfectionProvider { private sealed class SharedInfectionCache { private bool[] knownValues = Array.Empty(); private bool[] infectionValues = Array.Empty(); private float[] meters = Array.Empty(); private ulong[] actualClientIds = Array.Empty(); private int[] playerClientIds = Array.Empty(); private int validLength; public void EnsureCapacity(int length) { validLength = length; if (knownValues.Length < length) { int num = GrowCapacity(length); knownValues = new bool[num]; infectionValues = new bool[num]; meters = new float[num]; actualClientIds = new ulong[num]; playerClientIds = new int[num]; } } public void Clear() { Array.Clear(knownValues, 0, validLength); Array.Clear(infectionValues, 0, validLength); Array.Clear(meters, 0, validLength); Array.Clear(actualClientIds, 0, validLength); Array.Clear(playerClientIds, 0, validLength); validLength = 0; } public void Set(int index, bool hasValue, float meter) { knownValues[index] = false; infectionValues[index] = hasValue; meters[index] = meter; actualClientIds[index] = ulong.MaxValue; playerClientIds[index] = -1; } public void Set(int index, PlayerControllerB player, bool hasValue, float meter) { knownValues[index] = true; infectionValues[index] = hasValue; meters[index] = meter; actualClientIds[index] = (((Object)(object)player != (Object)null) ? player.actualClientId : ulong.MaxValue); playerClientIds[index] = (((Object)(object)player != (Object)null) ? ((int)player.playerClientId) : (-1)); } public bool TryGet(int index, PlayerControllerB player, out float meter) { bool hasInfection; return TryGet(index, player, out hasInfection, out meter) && hasInfection; } public bool TryGet(int index, PlayerControllerB player, out bool hasInfection, out float meter) { meter = 0f; hasInfection = false; if (index < 0 || index >= validLength || !knownValues[index]) { return false; } if ((Object)(object)player == (Object)null || actualClientIds[index] != player.actualClientId || playerClientIds[index] != (int)player.playerClientId) { knownValues[index] = false; infectionValues[index] = false; return false; } hasInfection = infectionValues[index]; meter = meters[index]; return true; } private static int GrowCapacity(int requiredCapacity) { int num; for (num = 16; num < requiredCapacity; num *= 2) { } return num; } } private const float SampleInterval = 0.5f; private const float AbsentInstanceScanIntervalMin = 1f; private const float AbsentInstanceScanIntervalMax = 10f; private const float CachedInstanceMembershipCheckInterval = 2f; private const float InfectionMeterEpsilon = 0.0001f; private const int MinimumInfectionCacheCapacity = 16; private static readonly SharedInfectionCache sharedInfectionCache = new SharedInfectionCache(); private static CadaverGrowthAI? cachedCadaverGrowthInstance; private static float nextInstanceScanTime; private static float nextSampleTime; private static float nextCachedInstanceMembershipCheckTime; private static float currentAbsentInstanceScanInterval = 1f; public static bool TryGetNormalizedInfection(PlayerControllerB player, out float infectionMeter) { return TryGetNormalizedInfection(player, -1, out infectionMeter); } public static bool TryGetNormalizedInfection(PlayerControllerB player, int knownPlayerSlot, out float infectionMeter) { bool hasInfection; return TryGetInfectionState(player, knownPlayerSlot, out hasInfection, out infectionMeter) && hasInfection; } public static bool TryGetInfectionState(PlayerControllerB player, int knownPlayerSlot, out bool hasInfection, out float infectionMeter) { infectionMeter = 0f; hasInfection = false; if ((Object)(object)player == (Object)null) { return false; } RefreshCache(); if (TryResolveInfectionIndex(player, knownPlayerSlot, out var infectionIndex)) { return sharedInfectionCache.TryGet(infectionIndex, player, out hasInfection, out infectionMeter); } return false; } private static void RefreshCache() { if (Time.unscaledTime < nextSampleTime) { return; } nextSampleTime = Time.unscaledTime + 0.5f; PlayerInfection[] array = ResolveCadaverGrowthInstance()?.playerInfections; if (array == null) { sharedInfectionCache.Clear(); return; } PlayerControllerB[] array2 = StartOfRound.Instance?.allPlayerScripts; if (array2 == null || array2.Length != array.Length) { sharedInfectionCache.Clear(); return; } sharedInfectionCache.EnsureCapacity(array.Length); for (int i = 0; i < array.Length; i++) { PlayerControllerB val = array2[i]; PlayerInfection val2 = array[i]; if ((Object)(object)val == (Object)null || val2 == null) { sharedInfectionCache.Set(i, hasValue: false, 0f); continue; } float num = Mathf.Clamp01(val2.infectionMeter); if (!val2.infected || !(num > 0.0001f)) { sharedInfectionCache.Set(i, val, hasValue: false, 0f); } else { sharedInfectionCache.Set(i, val, hasValue: true, num); } } } private static CadaverGrowthAI? ResolveCadaverGrowthInstance() { if ((Object)(object)cachedCadaverGrowthInstance != (Object)null) { if (IsCachedCadaverGrowthInstanceValid(cachedCadaverGrowthInstance)) { return cachedCadaverGrowthInstance; } ClearCachedCadaverGrowthInstance(); } if (Time.unscaledTime < nextInstanceScanTime) { return null; } nextInstanceScanTime = Time.unscaledTime + currentAbsentInstanceScanInterval; if ((Object)(object)RoundManager.Instance == (Object)null) { IncreaseAbsentInstanceScanBackoff(); return null; } for (int i = 0; i < RoundManager.Instance.SpawnedEnemies.Count; i++) { EnemyAI obj = RoundManager.Instance.SpawnedEnemies[i]; CadaverGrowthAI val = (CadaverGrowthAI)(object)((obj is CadaverGrowthAI) ? obj : null); if (val != null && IsCadaverGrowthInstanceUsable(val)) { cachedCadaverGrowthInstance = val; ResetAbsentInstanceScanBackoff(); return cachedCadaverGrowthInstance; } } IncreaseAbsentInstanceScanBackoff(); return null; } private static bool IsCadaverGrowthInstanceValid(CadaverGrowthAI? cadaverGrowth) { if (IsCadaverGrowthInstanceUsable(cadaverGrowth)) { return IsCadaverGrowthInSpawnedEnemies(cadaverGrowth); } return false; } private static bool IsCachedCadaverGrowthInstanceValid(CadaverGrowthAI? cadaverGrowth) { if (!IsCadaverGrowthInstanceUsable(cadaverGrowth)) { return false; } if (Time.unscaledTime < nextCachedInstanceMembershipCheckTime) { return true; } nextCachedInstanceMembershipCheckTime = Time.unscaledTime + 2f; return IsCadaverGrowthInSpawnedEnemies(cadaverGrowth); } private static bool IsCadaverGrowthInstanceUsable(CadaverGrowthAI? cadaverGrowth) { if ((Object)(object)cadaverGrowth == (Object)null || ((EnemyAI)cadaverGrowth).isEnemyDead || !((Component)cadaverGrowth).gameObject.activeInHierarchy) { return false; } if ((Object)(object)StartOfRound.Instance == (Object)null || cadaverGrowth.playerInfections == null) { return false; } if (cadaverGrowth.playerInfections.Length != StartOfRound.Instance.allPlayerScripts.Length) { return false; } return true; } private static bool IsCadaverGrowthInSpawnedEnemies(CadaverGrowthAI? cadaverGrowth) { if ((Object)(object)RoundManager.Instance == (Object)null) { return false; } for (int i = 0; i < RoundManager.Instance.SpawnedEnemies.Count; i++) { if ((Object)(object)RoundManager.Instance.SpawnedEnemies[i] == (Object)(object)cadaverGrowth) { return true; } } return false; } private static void ClearCachedCadaverGrowthInstance() { cachedCadaverGrowthInstance = null; sharedInfectionCache.Clear(); nextInstanceScanTime = 0f; nextCachedInstanceMembershipCheckTime = 0f; } internal static void InvalidateAll() { cachedCadaverGrowthInstance = null; sharedInfectionCache.Clear(); nextInstanceScanTime = 0f; nextSampleTime = 0f; nextCachedInstanceMembershipCheckTime = 0f; currentAbsentInstanceScanInterval = 1f; } private static bool TryResolveInfectionIndex(PlayerControllerB player, int knownPlayerSlot, out int infectionIndex) { infectionIndex = -1; PlayerControllerB[] array = StartOfRound.Instance?.allPlayerScripts; if (array == null || array.Length == 0) { return false; } if ((uint)knownPlayerSlot < (uint)array.Length && (Object)(object)array[knownPlayerSlot] == (Object)(object)player) { infectionIndex = knownPlayerSlot; return true; } int num = (int)player.playerClientId; if ((uint)num < (uint)array.Length && (Object)(object)array[num] == (Object)(object)player) { infectionIndex = num; return true; } for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] == (Object)(object)player) { infectionIndex = i; return true; } } return false; } private static void IncreaseAbsentInstanceScanBackoff() { currentAbsentInstanceScanInterval = Mathf.Min(currentAbsentInstanceScanInterval * 2f, 10f); } private static void ResetAbsentInstanceScanBackoff() { currentAbsentInstanceScanInterval = 1f; } } internal static class MyPluginInfo { public const string PluginGuid = "playerstatusbars"; public const string PluginName = "PlayerStatusBars"; public const string PluginVersion = "0.2.1"; } 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(); private PlayerStatusBarView?[] activeBars = Array.Empty(); private int activeBarCount; private int activePlayerSlotCount; private readonly Stack pooledBars = new Stack(); private float nextRefreshTime; private float nextDebugSummaryTime; private int refreshScanCursor; private void Start() { Plugin.LogDebug($"Runtime manager started active={((Component)this).gameObject.activeInHierarchy}."); } private void Update() { StatusBarConfig settings = Plugin.Settings; if (!settings.Enabled) { if (activeBarCount != 0 || !(Time.unscaledTime < nextRefreshTime)) { LogDebugBlocked("disabled"); ClearBars(); PlayerStatusSnapshotSync.ClearSnapshots(); CadaverGrowthInfectionProvider.InvalidateAll(); ScheduleNextRefresh(hasActiveBars: false); } return; } StartOfRound instance = StartOfRound.Instance; PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : instance?.localPlayerController); PlayerControllerB[] array = instance?.allPlayerScripts; if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null || array == null) { if (Time.unscaledTime >= nextRefreshTime) { ScheduleNextRefresh(hasActiveBars: false); LogDebugBlocked($"not-ready startOfRound={(Object)(object)instance != (Object)null} localPlayer={(Object)(object)val != (Object)null} allPlayers={array != null}"); ClearBars(); PlayerStatusSnapshotSync.ClearSnapshots(); CadaverGrowthInfectionProvider.InvalidateAll(); } return; } EnsureSlotCapacity(array.Length); bool flag = !ShouldSkipBarsForShipState(instance); if (!flag) { if (Time.unscaledTime >= nextRefreshTime) { LogDebugBlocked($"orbit-hidden inShipPhase={instance.inShipPhase} shipHasLanded={instance.shipHasLanded} shipDoorsEnabled={instance.shipDoorsEnabled} shipIsLeaving={instance.shipIsLeaving} dayStarted={TimeOfDay.Instance?.currentDayTimeStarted}"); } ClearBars(); PlayerStatusSnapshotSync.ClearSnapshots(); CadaverGrowthInfectionProvider.InvalidateAll(); ScheduleNextRefresh(hasActiveBars: false); return; } PlayerStatusSnapshotSync.Tick(instance, array); if (activeBarCount != 0 || !(Time.unscaledTime < nextRefreshTime)) { if (Time.unscaledTime >= nextRefreshTime) { RefreshPlayerSlice(instance, val, array); } if (activeBarCount != 0) { PlayerStatusFrameContext frameContext = CreateFrameContext(settings, val, flag); TickTrackedBars(in frameContext); } } } 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(); 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, bool canShowGroup) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Camera viewCamera = null; bool hasBillboardRotation = false; Quaternion rotation = Quaternion.identity; Vector3 observerPosition = ((Component)localPlayer).transform.position; if (canShowGroup) { viewCamera = StatusBarBillboard.ResolveViewCamera(); hasBillboardRotation = StatusBarBillboard.TryResolveBillboardRotation(viewCamera, out rotation); observerPosition = ResolveObserverPosition(localPlayer, viewCamera); } return new PlayerStatusFrameContext(settings, localPlayer, viewCamera, hasBillboardRotation, rotation, canShowGroup, observerPosition); } 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(); activeBars = Array.Empty(); 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 sealed class PlayerStatusBarStrip : MonoBehaviour { internal enum StripType { Health, Infection } private const float Width = 172f; private const float Height = 20f; private bool visible = true; private string label = string.Empty; private float currentValue; private float maxValue = 1f; private bool renderAsPercent; private bool labelOnly; 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(); if ((Object)(object)rectTransform == (Object)null) { rectTransform = ((Component)this).gameObject.AddComponent(); } 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(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; backgroundImage = val.GetComponent(); ((Graphic)backgroundImage).raycastTarget = false; border = val.GetComponent(); GameObject val2 = new GameObject("Fill", new Type[2] { typeof(RectTransform), typeof(Image) }); val2.transform.SetParent(val.transform, false); fillRect = val2.GetComponent(); 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(); ((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(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = new Vector2(6f, 1f); component2.offsetMax = new Vector2(-6f, -1f); text = val3.GetComponent(); ((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(string displayLabel, float value, float max, bool showPercent) { float num = Mathf.Clamp(value, 0f, (max <= 0f) ? 1f : max); float num2 = Mathf.Max(max, 0.0001f); if (label == displayLabel && Mathf.Approximately(currentValue, num) && Mathf.Approximately(maxValue, num2) && renderAsPercent == showPercent && !labelOnly) { visible = true; return; } label = displayLabel; currentValue = num; maxValue = num2; renderAsPercent = showPercent; labelOnly = false; visible = true; dirty = true; } public void SetLabelOnly(string displayLabel, float fillValue, float max) { float num = Mathf.Clamp(fillValue, 0f, (max <= 0f) ? 1f : max); float num2 = Mathf.Max(max, 0.0001f); if (label == displayLabel && Mathf.Approximately(currentValue, num) && Mathf.Approximately(maxValue, num2) && labelOnly) { visible = true; return; } label = displayLabel; currentValue = num; maxValue = num2; renderAsPercent = false; labelOnly = true; 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: 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 flag = ((stripType == StripType.Health) ? settings.ShowHealthText : settings.ShowInfectionText); string text = string.Empty; if (flag) { text = (labelOnly ? label : (renderAsPercent ? StatusBarTextCache.GetInfectionLabel(Mathf.RoundToInt(num * 100f)) : StatusBarTextCache.GetHealthLabel(Mathf.RoundToInt(currentValue), Mathf.RoundToInt(maxValue)))); } bool flag2 = backgroundColor != lastAppliedBackgroundColor || val != lastAppliedFillColor || borderColor != lastAppliedBorderColor; bool flag3 = !Mathf.Approximately(num, lastAppliedNormalized); bool flag4 = flag != lastAppliedTextEnabled; bool flag5 = text != lastAppliedText; bool flag6 = visible != lastAppliedVisible; if (dirty || flag2 || flag3 || flag4 || flag5 || flag6) { if (flag2) { ((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 || flag3) { ((Transform)fillRect).localScale = new Vector3(num, 1f, 1f); lastAppliedNormalized = num; } if (dirty || flag4) { ((Behaviour)this.text).enabled = flag; lastAppliedTextEnabled = flag; } if (flag && (dirty || flag5)) { ((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 int LowHealthRegenerationTarget = 20; private const int InferredCriticalHealth = 5; private const float LowHealthRegenerationInterval = 1f; private const float HealthDisplayStepInterval = 0.05f; private const int InfectionDisplayMinimumPercent = 1; private const int InfectionDisplayMaximumPercent = 99; private const float InfectionPredictionWindow = 0.5f; private const float InfectionDisplayStepInterval = 0.1f; private PlayerControllerB targetPlayer; private Transform? anchor; private ulong boundActualClientId = ulong.MaxValue; private int boundPlayerKey = -1; private PlayerStatusBarStrip healthStrip; private PlayerStatusBarStrip infectionStrip; private RectTransform healthStripRect; private RectTransform infectionStripRect; private Canvas worldCanvas; private RectTransform canvasRect; private int lastDisplayedHealth = int.MinValue; private int lastDisplayedInfectionPercent = int.MinValue; private bool wasHealthVisible; private bool wasInfectionVisible; private string lastDisplayedHealthLabel = string.Empty; private bool lastCanvasEnabled = true; private Camera? lastAppliedCanvasCamera; private string lastDebugVisibilityState = string.Empty; private float nextDebugVisibilityLogTime; private float lastAppliedUiScale = -1f; private float lastAppliedHealthBarYOffset = float.NaN; private float lastAppliedInfectionBarYOffset = float.NaN; private bool isLowHealthPredictionActive; private bool wasTargetDead; private bool hasObservedRawHealth; private bool hasObservedDamageTimestamp; private bool hasObservedRawCriticalState; private bool lastObservedRawCriticalState; private bool holdNaturalRecoveredLowHealthDisplay; private bool isInfectionPredictionActive; private bool hasDisplayedHealth; private bool hasDisplayedInfectionPercent; private bool wasUsingSnapshot; private bool lastDebugRawCriticalState; private bool lastDebugDamageSignalChanged; private bool lastDebugCriticalSignalChanged; private int lastObservedRawHealth = int.MinValue; private int predictionObservedRawHealth = int.MinValue; private int predictionStartHealth = int.MinValue; private int predictedLowHealth = int.MinValue; private int displayedHealth = int.MinValue; private int healthDisplayTarget = int.MinValue; private int naturalRecoveredRawHealth = int.MinValue; private int displayedInfectionPercent = int.MinValue; private int lastDebugRawHealth = int.MinValue; private int lastDebugDisplayHealthTarget = int.MinValue; private int lastDebugDisplayedHealth = int.MinValue; private float lowHealthPredictionStartTime; private float lastObservedDamageTimestamp; private float lastObservedInfectionMeter; private float lastObservedInfectionSampleTime; private float infectionPredictionStartMeter; private float infectionPredictionStartTime; private float infectionPredictionRate; private float nextHealthDisplayStepTime; private float nextInfectionDisplayStepTime; public int PlayerId { get { if (boundPlayerKey < 0) { if (!((Object)(object)targetPlayer != (Object)null)) { return -1; } return (int)targetPlayer.playerClientId; } return boundPlayerKey; } } 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(); worldCanvas.renderMode = (RenderMode)2; worldCanvas.overrideSorting = true; worldCanvas.sortingOrder = 50; canvasRect = ((Component)worldCanvas).GetComponent(); canvasRect.sizeDelta = new Vector2(220f, 84f); if (!TryCreateStrip("HealthStrip", out healthStrip)) { return false; } healthStripRect = ((Component)healthStrip).GetComponent(); if (!TryCreateStrip("InfectionStrip", out infectionStrip)) { return false; } infectionStripRect = ((Component)infectionStrip).GetComponent(); 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) { healthStrip.SetVisible(isVisible: false); } if ((Object)(object)infectionStrip != (Object)null) { infectionStrip.SetVisible(isVisible: false); } 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_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)targetPlayer == (Object)null) { return; } StatusBarConfig settings = context.Settings; if (!context.CanShowGroup) { HideAll(settings, "group-hidden", context.ViewCamera, -1f); return; } PlayerStatusSnapshot snapshot; bool flag = PlayerStatusSnapshotSync.TryGetSnapshot(targetPlayer, out snapshot); HandleSnapshotSourceTransition(flag); bool targetIsDead = ((!flag) ? (targetPlayer.isPlayerDead || targetPlayer.health <= 0) : (snapshot.IsDead || snapshot.Health <= 0)); HandleDeathStateTransition(targetIsDead); if (!IsTargetReadyForDisplay(targetIsDead)) { HideAll(settings, "target-not-ready", context.ViewCamera, -1f); return; } if (anchor == null) { anchor = ResolveAnchor(targetPlayer); } Vector3 val = (((Object)(object)anchor != (Object)null) ? anchor.position : ((Component)targetPlayer).transform.position); ((Component)this).transform.position = val + Vector3.up * context.AnchorYOffset; if (context.HasBillboardRotation) { SetBillboardRotation(context.BillboardRotation); } ApplyCanvasCamera(context.ViewCamera); ApplyCanvasScale(context.UiScale); float distanceSqr; bool flag2 = ShouldShowForDistance(in context, out distanceSqr); if (!flag2) { HideForDistance(context.ViewCamera, distanceSqr); return; } int rawHealth = (flag ? Mathf.Clamp(snapshot.Health, 0, 100) : Mathf.Clamp(targetPlayer.health, 0, 100)); bool rawCriticalState = (flag ? snapshot.IsCritical : (targetPlayer.criticallyInjured || targetPlayer.bleedingHeavily)); bool flag3 = ResolveDamageSignalChanged(targetPlayer.timeSinceTakingDamage); bool flag4 = ResolveCriticalSignalChanged(rawCriticalState); bool lowHealthSignalChanged = flag3 || flag4; int num = ResolveDisplayHealth(rawHealth, rawCriticalState, lowHealthSignalChanged, settings.CriticalHealthMode); int num2 = ResolveSteppedHealthDisplay(num); lastDebugRawHealth = rawHealth; lastDebugDisplayHealthTarget = num; lastDebugDisplayedHealth = num2; lastDebugRawCriticalState = rawCriticalState; lastDebugDamageSignalChanged = flag3; lastDebugCriticalSignalChanged = flag4; ApplyStripLayoutOffsets(settings); bool flag5 = true; LogDebugVisibility("visible", context.ViewCamera, distanceSqr); bool enabled = num < 20 || num2 < 20; healthStrip.SetFillColorOverride(enabled, CriticalHealthBarColor); if (flag5 && (!wasHealthVisible || num2 != lastDisplayedHealth || lastDisplayedHealthLabel != "HP")) { healthStrip.SetDisplay("HP", num2, 100f, showPercent: false); lastDisplayedHealth = num2; lastDisplayedHealthLabel = "HP"; } healthStrip.SetVisible(flag5); wasHealthVisible = flag5; float infectionMeter = 0f; bool hasInfection = false; if (flag && snapshot.IsInfectionKnown) { hasInfection = snapshot.HasInfection; infectionMeter = snapshot.InfectionMeter; } else if (!PlayerStatusSnapshotSync.TryGetReportedInfection(targetPlayer, boundPlayerKey, out hasInfection, out infectionMeter) && flag2) { hasInfection = CadaverGrowthInfectionProvider.TryGetNormalizedInfection(targetPlayer, boundPlayerKey, out infectionMeter); } bool flag6 = ((settings.InfectionDisplayMode == StatusBarConfig.InfectionBarDisplayMode.AlwaysVisible) ? flag2 : (flag2 && hasInfection)); if (flag6) { int num3 = ResolveDisplayInfectionPercent(infectionMeter, hasInfection); if (!wasInfectionVisible || num3 != lastDisplayedInfectionPercent) { infectionStrip.SetDisplay("INF", num3, 100f, showPercent: true); lastDisplayedInfectionPercent = num3; } infectionStrip.SetVisible(isVisible: true); wasInfectionVisible = true; } else { infectionStrip.SetVisible(isVisible: false); wasInfectionVisible = false; ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); lastDisplayedInfectionPercent = int.MinValue; } SetCanvasEnabled(flag5 || flag6); ApplyStripsIfDirty(settings); } private void HideForDistance(Camera? viewCamera, float distanceSqr) { LogDebugVisibility("distance-hidden", viewCamera, distanceSqr); SetCanvasEnabled(enabled: false); healthStrip.SetVisible(isVisible: false); infectionStrip.SetVisible(isVisible: false); if (wasHealthVisible) { ResetDisplayedHealth(); } if (wasInfectionVisible) { ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); } wasHealthVisible = false; wasInfectionVisible = false; } private bool IsTargetReadyForDisplay(bool targetIsDead) { if ((Object)(object)targetPlayer != (Object)null && !targetPlayer.disconnectedMidGame && !targetIsDead) { return ((Component)targetPlayer).gameObject.activeInHierarchy; } return false; } private void ResetRuntimeState() { lastDisplayedHealth = int.MinValue; lastDisplayedInfectionPercent = int.MinValue; wasHealthVisible = false; wasInfectionVisible = false; lastDisplayedHealthLabel = string.Empty; lastDebugVisibilityState = string.Empty; nextDebugVisibilityLogTime = 0f; lastAppliedCanvasCamera = null; lastAppliedUiScale = -1f; lastAppliedHealthBarYOffset = float.NaN; lastAppliedInfectionBarYOffset = float.NaN; wasTargetDead = false; hasObservedRawHealth = false; ResetLowHealthSignals(); ResetLowHealthPrediction(); ResetNaturalRecoveredLowHealthHold(); ResetDisplayedHealth(); ResetDebugHealthDecisionState(); ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); wasUsingSnapshot = false; } private void HandleSnapshotSourceTransition(bool hasSyncedSnapshot) { if (wasUsingSnapshot != hasSyncedSnapshot) { wasUsingSnapshot = hasSyncedSnapshot; lastDisplayedHealth = int.MinValue; lastDisplayedHealthLabel = string.Empty; lastDisplayedInfectionPercent = int.MinValue; ResetLowHealthSignals(); ResetLowHealthPrediction(); ResetNaturalRecoveredLowHealthHold(); ResetDisplayedHealth(); ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); } } private void HandleDeathStateTransition(bool targetIsDead) { if (targetIsDead) { if (!wasTargetDead) { wasTargetDead = true; lastDisplayedHealth = int.MinValue; lastDisplayedHealthLabel = string.Empty; ResetLowHealthSignals(); ResetLowHealthPrediction(); ResetNaturalRecoveredLowHealthHold(); ResetDisplayedHealth(); ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); } } else if (wasTargetDead) { wasTargetDead = false; lastDisplayedHealth = int.MinValue; lastDisplayedHealthLabel = string.Empty; ResetLowHealthSignals(); ResetLowHealthPrediction(); ResetNaturalRecoveredLowHealthHold(); ResetDisplayedHealth(); ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); } } private bool ResolveDamageSignalChanged(float damageTimestamp) { if (!hasObservedDamageTimestamp) { hasObservedDamageTimestamp = true; lastObservedDamageTimestamp = damageTimestamp; return false; } if (damageTimestamp == lastObservedDamageTimestamp) { return false; } lastObservedDamageTimestamp = damageTimestamp; return true; } private bool ResolveCriticalSignalChanged(bool rawCriticalState) { if (!hasObservedRawCriticalState) { hasObservedRawCriticalState = true; lastObservedRawCriticalState = rawCriticalState; return false; } if (rawCriticalState == lastObservedRawCriticalState) { return false; } lastObservedRawCriticalState = rawCriticalState; return true; } private int ResolveDisplayHealth(int rawHealth, bool rawCriticalState, bool lowHealthSignalChanged, StatusBarConfig.CriticalHealthSyncMode criticalHealthMode) { bool flag = !hasObservedRawHealth || rawHealth != lastObservedRawHealth; if (flag) { hasObservedRawHealth = true; lastObservedRawHealth = rawHealth; } if (rawHealth <= 0) { ResetLowHealthPrediction(); ResetNaturalRecoveredLowHealthHold(); return rawHealth; } if (holdNaturalRecoveredLowHealthDisplay) { if (!(rawHealth != naturalRecoveredRawHealth || lowHealthSignalChanged)) { return 20; } ResetNaturalRecoveredLowHealthHold(); } if (ShouldInferVanillaCriticalHealth(rawHealth, rawCriticalState, criticalHealthMode)) { if (!isLowHealthPredictionActive || rawHealth != predictionObservedRawHealth) { BeginLowHealthPrediction(5, rawHealth); } return ResolveLowHealthPredictionDisplay(rawHealth, flag || lowHealthSignalChanged, 5); } if (rawHealth >= 20) { if (isLowHealthPredictionActive) { bool num = PredictLowHealthFromElapsed() >= 20 && !flag; ResetLowHealthPrediction(); if (num) { BeginNaturalRecoveredLowHealthHold(rawHealth); return 20; } } return rawHealth; } ResetNaturalRecoveredLowHealthHold(); return ResolveLowHealthPredictionDisplay(rawHealth, flag || lowHealthSignalChanged, rawHealth); } private static bool ShouldInferVanillaCriticalHealth(int rawHealth, bool rawCriticalState, StatusBarConfig.CriticalHealthSyncMode criticalHealthMode) { if (criticalHealthMode == StatusBarConfig.CriticalHealthSyncMode.VanillaPrediction && rawCriticalState) { return rawHealth == 20; } return false; } private int ResolveLowHealthPredictionDisplay(int rawHealth, bool restartLowHealthPrediction, int predictionStartHealth) { if (!isLowHealthPredictionActive || rawHealth != predictionObservedRawHealth || restartLowHealthPrediction) { BeginLowHealthPrediction(predictionStartHealth, rawHealth); } if (PredictLowHealthFromElapsed() >= 20 && !restartLowHealthPrediction) { ResetLowHealthPrediction(); BeginNaturalRecoveredLowHealthHold(rawHealth); return 20; } return ResolvePredictedLowHealthDisplay(); } private int ResolveSteppedHealthDisplay(int targetHealth) { if (!hasDisplayedHealth) { hasDisplayedHealth = true; displayedHealth = targetHealth; healthDisplayTarget = targetHealth; nextHealthDisplayStepTime = Time.unscaledTime + 0.05f; return displayedHealth; } if (targetHealth == displayedHealth) { healthDisplayTarget = targetHealth; return displayedHealth; } if (targetHealth != healthDisplayTarget) { healthDisplayTarget = targetHealth; nextHealthDisplayStepTime = 0f; } if (Time.unscaledTime < nextHealthDisplayStepTime) { return displayedHealth; } displayedHealth += ((targetHealth > displayedHealth) ? 1 : (-1)); nextHealthDisplayStepTime = Time.unscaledTime + 0.05f; return displayedHealth; } private void BeginLowHealthPrediction(int displayHealth, int observedRawHealth) { isLowHealthPredictionActive = true; predictionObservedRawHealth = observedRawHealth; predictionStartHealth = displayHealth; predictedLowHealth = displayHealth; lowHealthPredictionStartTime = Time.time; } private int ResolvePredictedLowHealthDisplay() { return PredictLowHealthFromElapsed(); } private int PredictLowHealthFromElapsed() { float num = Time.time - lowHealthPredictionStartTime; int num2 = Mathf.Max(0, Mathf.FloorToInt(num / 1f)); predictedLowHealth = Mathf.Min(20, predictionStartHealth + num2); return predictedLowHealth; } private void BeginNaturalRecoveredLowHealthHold(int rawHealth) { holdNaturalRecoveredLowHealthDisplay = true; naturalRecoveredRawHealth = rawHealth; } private void ResetLowHealthPrediction() { isLowHealthPredictionActive = false; predictionObservedRawHealth = int.MinValue; predictionStartHealth = int.MinValue; predictedLowHealth = int.MinValue; lowHealthPredictionStartTime = 0f; } private void ResetLowHealthSignals() { hasObservedDamageTimestamp = false; lastObservedDamageTimestamp = 0f; hasObservedRawCriticalState = false; lastObservedRawCriticalState = false; } private void ResetDisplayedHealth() { hasDisplayedHealth = false; displayedHealth = int.MinValue; healthDisplayTarget = int.MinValue; nextHealthDisplayStepTime = 0f; } private void ResetDebugHealthDecisionState() { lastDebugRawHealth = int.MinValue; lastDebugDisplayHealthTarget = int.MinValue; lastDebugDisplayedHealth = int.MinValue; lastDebugRawCriticalState = false; lastDebugDamageSignalChanged = false; lastDebugCriticalSignalChanged = false; } private void ResetNaturalRecoveredLowHealthHold() { holdNaturalRecoveredLowHealthDisplay = false; naturalRecoveredRawHealth = int.MinValue; } private int ResolveDisplayInfectionPercent(float rawMeter, bool hasInfectionValue) { if (!hasInfectionValue) { ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); return 0; } rawMeter = Mathf.Clamp01(rawMeter); float unscaledTime = Time.unscaledTime; if (!isInfectionPredictionActive) { BeginInfectionPrediction(rawMeter, 0f); lastObservedInfectionMeter = rawMeter; lastObservedInfectionSampleTime = unscaledTime; return ResolveSteppedInfectionPercent(MeterToInfectionPercent(rawMeter)); } if (rawMeter < lastObservedInfectionMeter) { BeginInfectionPrediction(rawMeter, 0f); } else if (rawMeter > lastObservedInfectionMeter) { UpdateInfectionPredictionRate(rawMeter, unscaledTime); } lastObservedInfectionMeter = rawMeter; int targetPercent = MeterToInfectionPercent(PredictInfectionMeter()); return ResolveSteppedInfectionPercent(targetPercent); } private int ResolveSteppedInfectionPercent(int targetPercent) { if (!hasDisplayedInfectionPercent) { hasDisplayedInfectionPercent = true; displayedInfectionPercent = targetPercent; nextInfectionDisplayStepTime = Time.unscaledTime + 0.1f; return displayedInfectionPercent; } if (targetPercent < displayedInfectionPercent) { displayedInfectionPercent = targetPercent; nextInfectionDisplayStepTime = Time.unscaledTime + 0.1f; return displayedInfectionPercent; } if (Time.unscaledTime < nextInfectionDisplayStepTime) { return displayedInfectionPercent; } displayedInfectionPercent = Mathf.Min(targetPercent, displayedInfectionPercent + 1); nextInfectionDisplayStepTime = Time.unscaledTime + 0.1f; return displayedInfectionPercent; } private void UpdateInfectionPredictionRate(float rawMeter, float now) { float num = Mathf.Max(0.5f, now - lastObservedInfectionSampleTime); float rate = (rawMeter - lastObservedInfectionMeter) / num; BeginInfectionPrediction(rawMeter, rate); lastObservedInfectionSampleTime = now; } private void BeginInfectionPrediction(float rawMeter, float rate) { isInfectionPredictionActive = true; infectionPredictionStartMeter = rawMeter; infectionPredictionStartTime = Time.unscaledTime; infectionPredictionRate = rate; } private float PredictInfectionMeter() { float num = Mathf.Min(Time.unscaledTime - infectionPredictionStartTime, 0.5f); return Mathf.Clamp01(infectionPredictionStartMeter + infectionPredictionRate * num); } private static int MeterToInfectionPercent(float meter) { return Mathf.Clamp(Mathf.FloorToInt(Mathf.Clamp01(meter) * 100f), 1, 99); } private void ResetInfectionPrediction() { isInfectionPredictionActive = false; lastObservedInfectionMeter = 0f; lastObservedInfectionSampleTime = 0f; infectionPredictionStartMeter = 0f; infectionPredictionStartTime = 0f; infectionPredictionRate = 0f; } private void ResetDisplayedInfectionPercent() { hasDisplayedInfectionPercent = false; displayedInfectionPercent = int.MinValue; lastDisplayedInfectionPercent = int.MinValue; nextInfectionDisplayStepTime = 0f; } 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 ApplyStripLayoutOffsets(StatusBarConfig settings) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) float healthBarYOffset = settings.HealthBarYOffset; if (!Mathf.Approximately(lastAppliedHealthBarYOffset, healthBarYOffset)) { healthStripRect.anchoredPosition = new Vector2(0f, healthBarYOffset); lastAppliedHealthBarYOffset = healthBarYOffset; } float infectionBarYOffset = settings.InfectionBarYOffset; if (!Mathf.Approximately(lastAppliedInfectionBarYOffset, infectionBarYOffset)) { infectionStripRect.anchoredPosition = new Vector2(0f, infectionBarYOffset); lastAppliedInfectionBarYOffset = infectionBarYOffset; } } private void SetBillboardRotation(Quaternion billboardRotation) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) ((Component)this).transform.rotation = billboardRotation; } private void ApplyStripsIfDirty(StatusBarConfig settings) { healthStrip.ApplyIfDirty(settings); infectionStrip.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 void HideAll(StatusBarConfig settings, string reason, Camera? viewCamera, float distanceSqr) { LogDebugVisibility(reason, viewCamera, distanceSqr); SetCanvasEnabled(enabled: false); healthStrip.SetVisible(isVisible: false); infectionStrip.SetVisible(isVisible: false); wasHealthVisible = false; wasInfectionVisible = false; ResetInfectionPrediction(); ResetDisplayedInfectionPercent(); ApplyStripsIfDirty(settings); } private bool TryCreateStrip(string stripName, out PlayerStatusBarStrip strip) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0041: 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) strip = null; GameObject val = new GameObject(stripName, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((Component)worldCanvas).transform, false); ((Object)val).name = stripName; val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; strip = val.AddComponent(); strip.Initialize((!(stripName == "HealthStrip")) ? PlayerStatusBarStrip.StripType.Infection : PlayerStatusBarStrip.StripType.Health); return true; } 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_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: 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} displayHealthTarget={9} displayHealth={10} rawCritical={11} damageSignalChanged={12} criticalSignalChanged={13} predictionActive={14} naturalHold={15} controlled={16} dead={17} distance={18} canvasEnabled={19} camera='{20}' anchor='{21}' position={22} target={23}.", PlayerId, targetPlayer.playerClientId, targetPlayer.actualClientId, boundActualClientId, wasUsingSnapshot, targetPlayer.playerUsername, state, targetPlayer.health, lastDebugRawHealth, lastDebugDisplayHealthTarget, lastDebugDisplayedHealth, lastDebugRawCriticalState, lastDebugDamageSignalChanged, lastDebugCriticalSignalChanged, isLowHealthPredictionActive, holdNaturalRecoveredLowHealthDisplay, 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 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 bool CanShowGroup; 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, bool canShowGroup, 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_002e: 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) Settings = settings; LocalPlayer = localPlayer; ViewCamera = viewCamera; HasBillboardRotation = hasBillboardRotation; BillboardRotation = billboardRotation; CanShowGroup = canShowGroup; ObserverPosition = observerPosition; MaxDistanceSqr = settings.MaxDistanceSqr; AnchorYOffset = settings.AnchorYOffset; UiScale = settings.UiScale; } } internal readonly struct PlayerStatusSnapshot { public ulong ActualClientId { get; } public int Health { get; } public bool IsDead { get; } public bool IsCritical { get; } public bool IsInfectionKnown { get; } public bool HasInfection { get; } public float InfectionMeter { get; } public float ReceivedTime { get; } public PlayerStatusSnapshot(ulong actualClientId, int health, bool isDead, bool isCritical, bool isInfectionKnown, bool hasInfection, float infectionMeter, float receivedTime) { ActualClientId = actualClientId; Health = health; IsDead = isDead; IsCritical = isCritical; IsInfectionKnown = isInfectionKnown; HasInfection = hasInfection; InfectionMeter = infectionMeter; ReceivedTime = receivedTime; } } internal static class PlayerStatusSnapshotSync { [CompilerGenerated] private static class <>O { public static HandleNamedMessageDelegate <0>__HandleSnapshotMessage; public static HandleNamedMessageDelegate <1>__HandleInfectionReportMessage; } private const string SnapshotMessageName = "playerstatusbars.status.v1"; private const string InfectionReportMessageName = "playerstatusbars.infection-report.v1"; private const byte ProtocolVersion = 2; private const byte InfectionReportProtocolVersion = 1; private const byte DeadFlag = 1; private const byte CriticalFlag = 2; private const byte InfectionFlag = 4; private const byte InfectionKnownFlag = 8; private const float SnapshotInterval = 1f; private const float SnapshotTtl = 3f; private const float InfectionReportHeartbeatInterval = 0.5f; private const float InfectionReportTtl = 3f; private const int MinimumSnapshotCapacity = 16; private const int MinimumInfectionReportCapacity = 16; private const int SnapshotHeaderBytes = 2; private const int SnapshotEntryBytes = 12; private const int InfectionReportBytes = 12; private const int MaxSnapshotPayloadBytes = 2048; private const int MaxSnapshotEntries = 170; private static PlayerStatusSnapshot[] snapshots = Array.Empty(); private static bool[] hasSnapshots = Array.Empty(); private static bool[] hasInfectionReports = Array.Empty(); private static ulong[] infectionReportActualClientIds = Array.Empty(); private static bool[] infectionReportHasInfection = Array.Empty(); private static float[] infectionReportMeters = Array.Empty(); private static float[] infectionReportReceivedTimes = Array.Empty(); private static CustomMessagingManager? registeredMessagingManager; private static float nextSnapshotSendTime; private static float nextLocalInfectionReportSendTime; private static bool hasLocalInfectionReportState; private static byte lastLocalInfectionReportPlayerId; private static ulong lastLocalInfectionReportActualClientId; private static byte lastLocalInfectionReportFlags; private static byte lastLocalInfectionReportPercent; private static int validSnapshotLength; private static int validInfectionReportLength; public static void Tick(StartOfRound startOfRound, PlayerControllerB[] allPlayers) { NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = (((Object)(object)singleton != (Object)null) ? singleton.CustomMessagingManager : null); if ((Object)(object)singleton == (Object)null || val == null || !singleton.IsListening) { ClearSnapshots(); ClearInfectionReports(); ResetLocalInfectionReportState(); registeredMessagingManager = null; return; } EnsureRegistered(val); SendLocalInfectionReport(singleton, val, startOfRound, allPlayers); if (singleton.IsServer && !(Time.unscaledTime < nextSnapshotSendTime)) { nextSnapshotSendTime = Time.unscaledTime + 1f; SendSnapshotToClients(singleton, val, startOfRound, allPlayers); } } public static bool TryGetSnapshot(PlayerControllerB player, out PlayerStatusSnapshot snapshot) { snapshot = default(PlayerStatusSnapshot); if ((Object)(object)player == (Object)null) { return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && singleton.IsServer) { return false; } int num = (int)player.playerClientId; if (num < 0 || num >= validSnapshotLength || !hasSnapshots[num]) { return false; } PlayerStatusSnapshot playerStatusSnapshot = snapshots[num]; if (playerStatusSnapshot.ActualClientId != player.actualClientId || Time.unscaledTime - playerStatusSnapshot.ReceivedTime > 3f) { hasSnapshots[num] = false; return false; } snapshot = playerStatusSnapshot; return true; } internal static void ClearSnapshots() { ClearSnapshotValues(); ClearInfectionReports(); ResetLocalInfectionReportState(); nextSnapshotSendTime = 0f; } private static void ClearSnapshotValues() { Array.Clear(hasSnapshots, 0, validSnapshotLength); Array.Clear(snapshots, 0, validSnapshotLength); validSnapshotLength = 0; } private static void EnsureRegistered(CustomMessagingManager messagingManager) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (registeredMessagingManager != messagingManager) { if (registeredMessagingManager != null) { registeredMessagingManager.UnregisterNamedMessageHandler("playerstatusbars.status.v1"); registeredMessagingManager.UnregisterNamedMessageHandler("playerstatusbars.infection-report.v1"); } object obj = <>O.<0>__HandleSnapshotMessage; if (obj == null) { HandleNamedMessageDelegate val = HandleSnapshotMessage; <>O.<0>__HandleSnapshotMessage = val; obj = (object)val; } messagingManager.RegisterNamedMessageHandler("playerstatusbars.status.v1", (HandleNamedMessageDelegate)obj); object obj2 = <>O.<1>__HandleInfectionReportMessage; if (obj2 == null) { HandleNamedMessageDelegate val2 = HandleInfectionReportMessage; <>O.<1>__HandleInfectionReportMessage = val2; obj2 = (object)val2; } messagingManager.RegisterNamedMessageHandler("playerstatusbars.infection-report.v1", (HandleNamedMessageDelegate)obj2); registeredMessagingManager = messagingManager; } } private static void SendSnapshotToClients(NetworkManager networkManager, CustomMessagingManager messagingManager, StartOfRound startOfRound, PlayerControllerB[] allPlayers) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_014f: 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) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) if (allPlayers == null || allPlayers.Length == 0) { ClearSnapshotValues(); return; } if (!HasRemoteClients(networkManager)) { ClearSnapshotValues(); ClearInfectionReports(); return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(2048, (Allocator)2, -1); try { byte b = 2; byte b2 = 0; ((FastBufferWriter)(ref val)).WriteValueSafe(ref b, default(ForPrimitives)); int position = ((FastBufferWriter)(ref val)).Position; ((FastBufferWriter)(ref val)).WriteValueSafe(ref b2, default(ForPrimitives)); for (int i = 0; i < allPlayers.Length; i++) { if (b2 >= 170) { break; } PlayerControllerB val2 = allPlayers[i]; if (!ShouldIncludePlayer(startOfRound, val2)) { continue; } int num = (int)val2.playerClientId; if (num < 0 || num > 255) { continue; } int num2 = Mathf.Clamp(val2.health, 0, 255); byte b3 = 0; if (val2.isPlayerDead || num2 <= 0) { b3 = (byte)(b3 | 1u); } if (val2.criticallyInjured || val2.bleedingHeavily) { b3 = (byte)(b3 | 2u); } bool hasInfection; float infectionMeter; bool flag = TryGetReportedInfection(val2, i, out hasInfection, out infectionMeter); if (!flag) { flag = CadaverGrowthInfectionProvider.TryGetInfectionState(val2, i, out hasInfection, out infectionMeter); } byte b4 = 0; if (flag) { b3 = (byte)(b3 | 8u); b4 = (byte)Mathf.Clamp(Mathf.RoundToInt(infectionMeter * 100f), 0, 100); if (hasInfection && b4 > 0) { b3 = (byte)(b3 | 4u); } } byte b5 = (byte)num; ulong actualClientId = val2.actualClientId; byte b6 = (byte)num2; ((FastBufferWriter)(ref val)).WriteValueSafe(ref b5, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref actualClientId, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref b6, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref b3, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref b4, default(ForPrimitives)); b2++; } int position2 = ((FastBufferWriter)(ref val)).Position; ((FastBufferWriter)(ref val)).Seek(position); ((FastBufferWriter)(ref val)).WriteValueSafe(ref b2, default(ForPrimitives)); ((FastBufferWriter)(ref val)).Seek(position2); messagingManager.SendNamedMessageToAll("playerstatusbars.status.v1", val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } private static void SendLocalInfectionReport(NetworkManager networkManager, CustomMessagingManager messagingManager, StartOfRound startOfRound, PlayerControllerB[] allPlayers) { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010e: 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_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) if (!networkManager.IsClient || networkManager.IsServer) { return; } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : startOfRound?.localPlayerController); if ((Object)(object)val == (Object)null || allPlayers == null || allPlayers.Length == 0) { return; } int num = (int)val.playerClientId; if (num < 0 || num > 255) { return; } int knownPlayerSlot = ResolvePlayerSlot(val, allPlayers, num); bool flag; bool hasInfection; float infectionMeter; if (val.isPlayerDead || val.health <= 0) { flag = true; hasInfection = false; infectionMeter = 0f; } else { flag = CadaverGrowthInfectionProvider.TryGetInfectionState(val, knownPlayerSlot, out hasInfection, out infectionMeter); } byte b = 0; if (flag) { b = (byte)(b | 8u); } byte b2 = 0; if (flag && hasInfection) { b2 = (byte)Mathf.Clamp(Mathf.RoundToInt(infectionMeter * 100f), 0, 100); if (b2 > 0) { b = (byte)(b | 4u); } } byte playerId = (byte)num; ulong actualClientId = val.actualClientId; if (!ShouldSendLocalInfectionReport(playerId, actualClientId, b, b2)) { return; } FastBufferWriter val2 = default(FastBufferWriter); ((FastBufferWriter)(ref val2))..ctor(12, (Allocator)2, -1); try { byte b3 = 1; ((FastBufferWriter)(ref val2)).WriteValueSafe(ref b3, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe(ref playerId, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe(ref actualClientId, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe(ref b, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe(ref b2, default(ForPrimitives)); messagingManager.SendNamedMessage("playerstatusbars.infection-report.v1", 0uL, val2, (NetworkDelivery)3); hasLocalInfectionReportState = true; lastLocalInfectionReportPlayerId = playerId; lastLocalInfectionReportActualClientId = actualClientId; lastLocalInfectionReportFlags = b; lastLocalInfectionReportPercent = b2; nextLocalInfectionReportSendTime = Time.unscaledTime + 0.5f; } finally { ((IDisposable)(FastBufferWriter)(ref val2)).Dispose(); } } private static bool ShouldSendLocalInfectionReport(byte playerId, ulong actualClientId, byte flags, byte infectionPercent) { if (!hasLocalInfectionReportState) { return (flags & 8) != 0; } if (playerId != lastLocalInfectionReportPlayerId || actualClientId != lastLocalInfectionReportActualClientId || flags != lastLocalInfectionReportFlags || infectionPercent != lastLocalInfectionReportPercent) { return true; } return Time.unscaledTime >= nextLocalInfectionReportSendTime; } private static bool HasRemoteClients(NetworkManager networkManager) { foreach (ulong connectedClientsId in networkManager.ConnectedClientsIds) { if (connectedClientsId != 0L) { return true; } } return false; } private static void HandleSnapshotMessage(ulong senderClientId, FastBufferReader reader) { //IL_0026: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)NetworkManager.Singleton == (Object)null || senderClientId != 0L || !((FastBufferReader)(ref reader)).TryBeginRead(2)) { return; } byte b = 0; byte b2 = 0; ((FastBufferReader)(ref reader)).ReadValueSafe(ref b, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b2, default(ForPrimitives)); if (b != 2) { return; } ClearSnapshotValues(); float unscaledTime = Time.unscaledTime; for (int i = 0; i < b2; i++) { if (!((FastBufferReader)(ref reader)).TryBeginRead(12)) { break; } byte b3 = 0; ulong actualClientId = 0uL; byte health = 0; byte b4 = 0; byte b5 = 0; ((FastBufferReader)(ref reader)).ReadValueSafe(ref b3, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref actualClientId, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref health, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b4, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b5, default(ForPrimitives)); EnsureSnapshotCapacity(b3 + 1); snapshots[b3] = new PlayerStatusSnapshot(actualClientId, health, (b4 & 1) != 0, (b4 & 2) != 0, (b4 & 8) != 0, (b4 & 4) != 0, Mathf.Clamp01((float)(int)b5 / 100f), unscaledTime); hasSnapshots[b3] = true; } } private static void HandleInfectionReportMessage(ulong senderClientId, FastBufferReader reader) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || !((FastBufferReader)(ref reader)).TryBeginRead(12)) { return; } byte b = 0; byte reportedPlayerId = 0; ulong num = 0uL; byte b2 = 0; byte b3 = 0; ((FastBufferReader)(ref reader)).ReadValueSafe(ref b, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref reportedPlayerId, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref num, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b2, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b3, default(ForPrimitives)); if (b == 1 && num == senderClientId && TryResolveReportedPlayerId(senderClientId, reportedPlayerId, out var playerId)) { EnsureInfectionReportCapacity(playerId + 1); if ((b2 & 8) == 0) { hasInfectionReports[playerId] = false; return; } infectionReportActualClientIds[playerId] = num; infectionReportHasInfection[playerId] = (b2 & 4u) != 0 && b3 > 0; infectionReportMeters[playerId] = Mathf.Clamp01((float)(int)b3 / 100f); infectionReportReceivedTimes[playerId] = Time.unscaledTime; hasInfectionReports[playerId] = true; } } private static bool ShouldIncludePlayer(StartOfRound startOfRound, PlayerControllerB player) { if ((Object)(object)startOfRound == (Object)null || (Object)(object)player == (Object)null || player.disconnectedMidGame || !((Component)player).gameObject.activeInHierarchy) { return false; } if (player.isPlayerControlled) { return true; } return startOfRound.ClientPlayerList.ContainsKey(player.actualClientId); } private static int ResolvePlayerSlot(PlayerControllerB player, PlayerControllerB[] allPlayers, int fallbackPlayerId) { if ((uint)fallbackPlayerId < (uint)allPlayers.Length && (Object)(object)allPlayers[fallbackPlayerId] == (Object)(object)player) { return fallbackPlayerId; } for (int i = 0; i < allPlayers.Length; i++) { if ((Object)(object)allPlayers[i] == (Object)(object)player) { return i; } } return fallbackPlayerId; } private static bool TryResolveReportedPlayerId(ulong senderClientId, byte reportedPlayerId, out int playerId) { playerId = -1; StartOfRound instance = StartOfRound.Instance; PlayerControllerB[] array = instance?.allPlayerScripts; if ((Object)(object)instance == (Object)null || array == null) { return false; } if (instance.ClientPlayerList.TryGetValue(senderClientId, out var value) && (uint)value < (uint)array.Length && (Object)(object)array[value] != (Object)null && array[value].actualClientId == senderClientId) { playerId = value; return true; } if (reportedPlayerId < array.Length && (Object)(object)array[reportedPlayerId] != (Object)null && array[reportedPlayerId].actualClientId == senderClientId) { playerId = reportedPlayerId; return true; } return false; } internal static bool TryGetReportedInfection(PlayerControllerB player, int knownPlayerSlot, out bool hasInfection, out float infectionMeter) { hasInfection = false; infectionMeter = 0f; if ((Object)(object)player == (Object)null) { return false; } if (TryGetReportedInfectionAtIndex(knownPlayerSlot, player, out hasInfection, out infectionMeter)) { return true; } int num = (int)player.playerClientId; if (num != knownPlayerSlot) { return TryGetReportedInfectionAtIndex(num, player, out hasInfection, out infectionMeter); } return false; } private static bool TryGetReportedInfectionAtIndex(int index, PlayerControllerB player, out bool hasInfection, out float infectionMeter) { hasInfection = false; infectionMeter = 0f; if (index < 0 || index >= validInfectionReportLength || !hasInfectionReports[index]) { return false; } if (infectionReportActualClientIds[index] != player.actualClientId || Time.unscaledTime - infectionReportReceivedTimes[index] > 3f) { hasInfectionReports[index] = false; return false; } hasInfection = infectionReportHasInfection[index]; infectionMeter = infectionReportMeters[index]; return true; } private static void EnsureSnapshotCapacity(int requiredLength) { if (requiredLength > validSnapshotLength) { if (hasSnapshots.Length < requiredLength) { int newSize = GrowCapacity(requiredLength, 16); Array.Resize(ref hasSnapshots, newSize); Array.Resize(ref snapshots, newSize); } validSnapshotLength = requiredLength; } } private static void EnsureInfectionReportCapacity(int requiredLength) { if (requiredLength > validInfectionReportLength) { if (hasInfectionReports.Length < requiredLength) { int newSize = GrowCapacity(requiredLength, 16); Array.Resize(ref hasInfectionReports, newSize); Array.Resize(ref infectionReportActualClientIds, newSize); Array.Resize(ref infectionReportHasInfection, newSize); Array.Resize(ref infectionReportMeters, newSize); Array.Resize(ref infectionReportReceivedTimes, newSize); } validInfectionReportLength = requiredLength; } } private static void ClearInfectionReports() { Array.Clear(hasInfectionReports, 0, validInfectionReportLength); Array.Clear(infectionReportActualClientIds, 0, validInfectionReportLength); Array.Clear(infectionReportHasInfection, 0, validInfectionReportLength); Array.Clear(infectionReportMeters, 0, validInfectionReportLength); Array.Clear(infectionReportReceivedTimes, 0, validInfectionReportLength); validInfectionReportLength = 0; } private static void ResetLocalInfectionReportState() { nextLocalInfectionReportSendTime = 0f; hasLocalInfectionReportState = false; lastLocalInfectionReportPlayerId = 0; lastLocalInfectionReportActualClientId = 0uL; lastLocalInfectionReportFlags = 0; lastLocalInfectionReportPercent = 0; } private static int GrowCapacity(int requiredCapacity, int minimumCapacity) { int num; for (num = minimumCapacity; num < requiredCapacity; num *= 2) { } return num; } } [BepInPlugin("playerstatusbars", "PlayerStatusBars", "0.2.1")] public sealed class Plugin : BaseUnityPlugin { private const string RuntimeObjectName = "PlayerStatusBars.Runtime"; private const string LegacyConfigFileName = "Codex.OtherPlayerStatusBars.cfg"; private const string LcChineseProjectGuid = "cn.codex.v81testchn"; private const string LcChineseProjectConfigFileName = "LC Chinese Project.cfg"; private const string LcChineseProjectPluginDirectoryName = "V81TestChn"; private const string LcChineseProjectPluginFileName = "V81TestChn.dll"; private static GameObject? runtimeObject; private static PlayerStatusBarManager? runtimeManager; internal static ManualLogSource Log { get; private set; } internal static StatusBarConfig Settings { get; private set; } internal static bool UseChineseCriticalLabel { get; private set; } internal static bool UseChineseConfigDescriptions { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; MigrateLegacyConfigFile(); UseChineseConfigDescriptions = DetectLcChineseProject(); UseChineseCriticalLabel = UseChineseConfigDescriptions; Settings = StatusBarConfig.Create(((BaseUnityPlugin)this).Config, UseChineseConfigDescriptions); 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 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(); if ((Object)(object)runtimeManager == (Object)null) { runtimeManager = runtimeObject.AddComponent(); } LogDebug("Runtime manager created."); } private static bool DetectLcChineseProject() { foreach (PluginInfo value3 in Chainloader.PluginInfos.Values) { string guid = value3.Metadata.GUID ?? string.Empty; string value = value3.Metadata.Name ?? string.Empty; string value2 = value3.Location ?? string.Empty; if (IsLcChineseProjectGuid(guid) || LooksLikeLcChineseProject(value) || LooksLikeLcChineseProject(value2)) { return true; } } if (File.Exists(Path.Combine(Paths.ConfigPath, "LC Chinese Project.cfg"))) { return true; } if (File.Exists(Path.Combine(Paths.PluginPath, "V81TestChn", "V81TestChn.dll"))) { return true; } return false; } private static bool IsLcChineseProjectGuid(string guid) { return string.Equals(guid, "cn.codex.v81testchn", StringComparison.OrdinalIgnoreCase); } private static bool LooksLikeLcChineseProject(string value) { if (!Contains(value, "cn.codex.v81testchn") && !Contains(value, "LC Chinese Project") && !Contains(value, "V81TestChn")) { return Contains(value, "V81TestChn.dll"); } return true; } private static bool Contains(string value, string term) { return value.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0; } } internal static class StatusBarBillboard { private static int cachedCameraFrame = -1; private static Camera? cachedCamera; internal static void ApplyBillboardRotation(Transform target, Camera? viewCamera) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (TryResolveBillboardRotation(viewCamera, out var rotation)) { target.rotation = rotation; } } internal static bool TryResolveBillboardRotation(Camera? viewCamera, out Quaternion rotation) { //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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) rotation = Quaternion.identity; if ((Object)(object)viewCamera == (Object)null) { return false; } Vector3 forward = ((Component)viewCamera).transform.forward; Vector3 up = ((Component)viewCamera).transform.up; if (forward == Vector3.zero || up == Vector3.zero) { return false; } rotation = Quaternion.LookRotation(forward, up); return true; } internal static Camera? ResolveViewCamera() { if (cachedCameraFrame == Time.frameCount) { return cachedCamera; } cachedCameraFrame = Time.frameCount; cachedCamera = ResolveViewCameraUncached(); return cachedCamera; } private static Camera? ResolveViewCameraUncached() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null) { if ((Object)(object)instance.activeCamera != (Object)null && ((Behaviour)instance.activeCamera).enabled) { return instance.activeCamera; } PlayerControllerB localPlayerController = instance.localPlayerController; if ((Object)(object)localPlayerController != (Object)null && !localPlayerController.isPlayerDead && (Object)(object)localPlayerController.gameplayCamera != (Object)null && ((Behaviour)localPlayerController.gameplayCamera).enabled) { return localPlayerController.gameplayCamera; } if ((Object)(object)instance.spectateCamera != (Object)null && ((Behaviour)instance.spectateCamera).enabled) { return instance.spectateCamera; } } if (!((Object)(object)Camera.main != (Object)null)) { return Camera.current; } return Camera.main; } } internal sealed class StatusBarConfig { public enum InfectionBarDisplayMode { AlwaysVisible, ShowOnlyWhenInfected } public enum CriticalHealthSyncMode { VanillaPrediction, TrustRawHealthAt20 } public enum ColorPreset { Green, Red, Orange, Yellow, Blue, Cyan, White, Black, Slate } private readonly ConfigEntry enabled; private readonly bool useChineseDescriptions; private readonly ConfigEntry hideInOrbit; private readonly ConfigEntry maxDistance; private readonly ConfigEntry headOffset; private readonly ConfigEntry barSpacing; private readonly ConfigEntry healthBarYOffset; private readonly ConfigEntry infectionBarYOffset; private readonly ConfigEntry uiScale; private readonly ConfigEntry showHealthText; private readonly ConfigEntry showInfectionText; private readonly ConfigEntry healthColor; private readonly ConfigEntry infectionColor; private readonly ConfigEntry backgroundColor; private readonly ConfigEntry infectionDisplayMode; private readonly ConfigEntry criticalHealthMode; private readonly ConfigEntry debugLogging; private Color cachedHealthFillColor; private Color cachedInfectionFillColor; private Color cachedBackgroundColor; private Color cachedBorderColor; private float maxDistanceSqr; private float anchorYOffset; public bool Enabled => enabled.Value; public float MaxDistance => maxDistance.Value; public float MaxDistanceSqr => maxDistanceSqr; public float HeadOffset => headOffset.Value; public float AnchorYOffset => anchorYOffset; public float BarSpacing => barSpacing.Value; public float HealthBarYOffset => healthBarYOffset.Value; public float InfectionBarYOffset => infectionBarYOffset.Value; public float UiScale => uiScale.Value; public bool HideInOrbit => hideInOrbit.Value; public bool ShowHealthText => showHealthText.Value; public bool ShowInfectionText => showInfectionText.Value; public bool DebugLogging => debugLogging.Value; public InfectionBarDisplayMode InfectionDisplayMode => infectionDisplayMode.Value; public CriticalHealthSyncMode CriticalHealthMode => criticalHealthMode.Value; public int Revision { get; private set; } public event Action? SettingsChanged; public static StatusBarConfig Create(ConfigFile configFile) { return Create(configFile, useChineseDescriptions: false); } public static StatusBarConfig Create(ConfigFile configFile, bool useChineseDescriptions) { return new StatusBarConfig(configFile, useChineseDescriptions); } private StatusBarConfig(ConfigFile configFile, bool useChineseDescriptions) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Expected O, but got Unknown //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Expected O, but got Unknown this.useChineseDescriptions = useChineseDescriptions; enabled = configFile.Bind("General", "Enabled", true, Description("Enable other player status bars.", "启用其他玩家状态条。")); hideInOrbit = configFile.Bind("General", "Hide In Orbit", true, Description("Hide player status bars while the ship is still in orbit.", "飞船仍在轨道阶段时隐藏玩家状态条。")); maxDistance = configFile.Bind("General", "Max Display Distance", 18f, new ConfigDescription(Description("Hide other player status bars beyond this distance.", "超过此距离时隐藏其他玩家状态条。"), (AcceptableValueBase)(object)new AcceptableValueRange(2f, 80f), Array.Empty())); headOffset = configFile.Bind("Layout", "Head Offset", 0.65f, new ConfigDescription(Description("Vertical world-space offset above the player head anchor.", "相对于玩家头部锚点的世界空间垂直偏移。"), (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); barSpacing = configFile.Bind("Layout", "Bar Spacing", 14f, new ConfigDescription(Description("Vertical spacing between the health bar and infection bar.", "血条与感染条之间的垂直间距。"), (AcceptableValueBase)(object)new AcceptableValueRange(6f, 48f), Array.Empty())); healthBarYOffset = configFile.Bind("Layout", "Health Bar Y Offset", 0f, new ConfigDescription(Description("Local UI Y offset for the health bar.", "血条的本地 UI Y 轴偏移。"), (AcceptableValueBase)(object)new AcceptableValueRange(-64f, 64f), Array.Empty())); infectionBarYOffset = configFile.Bind("Layout", "Infection Bar Y Offset", 25f, new ConfigDescription(Description("Local UI Y offset for the infection bar.", "感染条的本地 UI Y 轴偏移。"), (AcceptableValueBase)(object)new AcceptableValueRange(-64f, 64f), Array.Empty())); uiScale = configFile.Bind("Layout", "UI Scale", 0.0085f, new ConfigDescription(Description("World-space scale of the player status bar canvas.", "玩家状态条画布的世界空间缩放。"), (AcceptableValueBase)(object)new AcceptableValueRange(0.003f, 0.05f), Array.Empty())); showHealthText = configFile.Bind("Text", "Show Health Text", true, Description("Show health numbers on the health bar.", "在血条上显示血量数字。")); showInfectionText = configFile.Bind("Text", "Show Infection Text", true, Description("Show infection percentage on the infection bar.", "在感染条上显示感染百分比。")); healthColor = configFile.Bind("Colors", "Health Bar Color", ColorPreset.Green, Description("Fill color preset for the health bar.", "血条填充颜色预设。")); infectionColor = configFile.Bind("Colors", "Infection Bar Color", ColorPreset.Orange, Description("Fill color preset for the infection bar.", "感染条填充颜色预设。")); backgroundColor = configFile.Bind("Colors", "Background Color", ColorPreset.Slate, Description("Background color preset for both bars.", "血条和感染条的背景颜色预设。")); infectionDisplayMode = configFile.Bind("General", "Infection Bar Display Mode", InfectionBarDisplayMode.ShowOnlyWhenInfected, Description("Always show the infection bar, or only show it when infection is above zero.", "始终显示感染条,或仅在感染值大于零时显示。")); criticalHealthMode = configFile.Bind("Compatibility", "Critical Health Sync Mode", CriticalHealthSyncMode.VanillaPrediction, Description("VanillaPrediction infers 5 HP when remote vanilla clients expose critical state at stale 20 HP. TrustRawHealthAt20 keeps 20 HP for active-bleed or custom-injury mods.", "VanillaPrediction 会在远端原版客户端显示 20 血但处于重伤状态时推断为 5 血;TrustRawHealthAt20 会信任 20 血,适合主动流血或自定义重伤模组。")); debugLogging = configFile.Bind("Debug", "Debug Logging", false, Description("Write throttled diagnostic logs for player status bar creation, filtering, visibility, camera state, and health display decisions.", "写入节流诊断日志,用于排查玩家状态条创建、过滤、可见性、相机状态和血量显示决策。")); Subscribe(enabled); Subscribe(hideInOrbit); Subscribe(maxDistance); Subscribe(headOffset); Subscribe(barSpacing); Subscribe(healthBarYOffset); Subscribe(infectionBarYOffset); Subscribe(uiScale); Subscribe(showHealthText); Subscribe(showInfectionText); Subscribe(healthColor); Subscribe(infectionColor); Subscribe(backgroundColor); Subscribe(infectionDisplayMode); Subscribe(criticalHealthMode); Subscribe(debugLogging); UpdateCachedValues(); } 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 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 void Subscribe(ConfigEntry entry) { entry.SettingChanged += delegate { NotifySettingsChanged(); }; } 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_006f: 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_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) cachedHealthFillColor = ResolveColor(healthColor.Value, 0.95f); cachedInfectionFillColor = ResolveColor(infectionColor.Value, 0.95f); cachedBackgroundColor = ResolveColor(backgroundColor.Value, 0.78f); cachedBorderColor = cachedBackgroundColor; cachedBorderColor.a = 1f; cachedBorderColor = Color.Lerp(cachedBorderColor, Color.white, 0.35f); float value = maxDistance.Value; maxDistanceSqr = value * value; anchorYOffset = headOffset.Value + barSpacing.Value * uiScale.Value; } 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; } private string Description(string english, string chinese) { if (!useChineseDescriptions) { return english; } return chinese; } } internal static class StatusBarTextCache { private const int CachedValueCount = 101; private static readonly string[] HealthLabels = BuildHealthLabels(); private static readonly string[] InfectionLabels = BuildInfectionLabels(); public static string GetHealthLabel(int currentHealth, int maxHealth) { if (maxHealth == 100 && currentHealth >= 0 && currentHealth < HealthLabels.Length) { return HealthLabels[currentHealth]; } return $"HP {currentHealth}/{maxHealth}"; } public static string GetInfectionLabel(int percent) { if (percent >= 0 && percent < InfectionLabels.Length) { return InfectionLabels[percent]; } return $"INF {percent}%"; } private static string[] BuildHealthLabels() { string[] array = new string[101]; for (int i = 0; i < array.Length; i++) { array[i] = $"HP {i}/100"; } return array; } private static string[] BuildInfectionLabels() { string[] array = new string[101]; for (int i = 0; i < array.Length; i++) { array[i] = $"INF {i}%"; } return array; } } }