Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Player Status Bars v0.2.1
BepInEx/plugins/PlayerStatusBars/PlayerStatusBars.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.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<bool>(); private bool[] infectionValues = Array.Empty<bool>(); private float[] meters = Array.Empty<float>(); private ulong[] actualClientIds = Array.Empty<ulong>(); private int[] playerClientIds = Array.Empty<int>(); 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<PlayerStatusBarView>(); private PlayerStatusBarView?[] activeBars = Array.Empty<PlayerStatusBarView>(); private int activeBarCount; private int activePlayerSlotCount; private readonly Stack<PlayerStatusBarView> pooledBars = new Stack<PlayerStatusBarView>(); private float nextRefreshTime; private float nextDebugSummaryTime; private int refreshScanCursor; private 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<PlayerStatusBarView>(); if (playerStatusBarView.Initialize(player, playerKey)) { return playerStatusBarView; } Object.Destroy((Object)(object)val); return null; } private void ReleaseBarView(PlayerStatusBarView view) { view.Release(); ((Component)view).transform.SetParent(((Component)this).transform, false); pooledBars.Push(view); } private void TickTrackedBars(in PlayerStatusFrameContext frameContext) { for (int i = 0; i < activeBarCount; i++) { PlayerStatusBarView playerStatusBarView = activeBars[i]; if ((Object)(object)playerStatusBarView != (Object)null) { playerStatusBarView.Tick(in frameContext); } } } private void AddActiveBar(PlayerStatusBarView view) { EnsureActiveBarCapacity(activeBarCount + 1); activeBars[activeBarCount] = view; activeBarCount++; } private void RemoveActiveBar(PlayerStatusBarView view) { for (int i = 0; i < activeBarCount; i++) { if (!((Object)(object)activeBars[i] != (Object)(object)view)) { int num = activeBarCount - 1; activeBars[i] = activeBars[num]; activeBars[num] = null; activeBarCount = num; break; } } } private void EnsureActiveBarCapacity(int requiredCapacity) { if (activeBars.Length < requiredCapacity) { PlayerStatusBarView[] destinationArray = new PlayerStatusBarView[GrowSlotCapacity(requiredCapacity)]; if (activeBarCount > 0) { Array.Copy(activeBars, destinationArray, activeBarCount); } activeBars = destinationArray; } } private static PlayerStatusFrameContext CreateFrameContext(StatusBarConfig settings, PlayerControllerB localPlayer, 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<PlayerStatusBarView>(); activeBars = Array.Empty<PlayerStatusBarView>(); activeBarCount = 0; while (pooledBars.Count > 0) { PlayerStatusBarView playerStatusBarView2 = pooledBars.Pop(); if ((Object)(object)playerStatusBarView2 != (Object)null) { Object.Destroy((Object)(object)((Component)playerStatusBarView2).gameObject); } } } private void OnEnable() { Plugin.LogDebug("Runtime manager enabled."); Plugin.Settings.SettingsChanged += HandleSettingsChanged; } private void OnDisable() { Plugin.LogDebug($"Runtime manager disabled tracked={activeBarCount}."); Plugin.Settings.SettingsChanged -= HandleSettingsChanged; } private void HandleSettingsChanged() { nextRefreshTime = 0f; } private void LogDebugBlocked(string reason) { if (Plugin.Settings.DebugLogging && !(Time.unscaledTime < nextDebugSummaryTime)) { nextDebugSummaryTime = Time.unscaledTime + 5f; Plugin.LogDebug($"Refresh blocked reason={reason} tracked={activeBarCount}."); } } private static void LogDebugPlayerSkip(int slot, PlayerControllerB player, string reason) { if (Plugin.Settings.DebugLogging && !(reason == "local-player")) { Plugin.LogDebug(string.Format("Skipped slot={0} reason={1} clientId={2} name='{3}' controlled={4} dead={5} health={6} active={7}.", slot, reason, ((Object)(object)player != (Object)null) ? player.playerClientId.ToString() : "null", ((Object)(object)player != (Object)null) ? player.playerUsername : "null", (Object)(object)player != (Object)null && player.isPlayerControlled, (Object)(object)player != (Object)null && player.isPlayerDead, ((Object)(object)player != (Object)null) ? player.health : (-1), (Object)(object)player != (Object)null && ((Component)player).gameObject.activeInHierarchy)); } } private void LogDebugSummary(StartOfRound startOfRound, PlayerControllerB localPlayer, int playerSlots, int createdCount, int existingCount, int skippedCount) { if (Plugin.Settings.DebugLogging && !(Time.unscaledTime < nextDebugSummaryTime)) { nextDebugSummaryTime = Time.unscaledTime + 5f; Camera val = StatusBarBillboard.ResolveViewCamera(); Plugin.LogDebug(string.Format("Refresh summary slots={0} tracked={1} created={2} existing={3} skipped={4} localClient={5} inShipPhase={6} shipHasLanded={7} shipDoorsEnabled={8} shipIsLeaving={9} dayStarted={10} camera='{11}'.", playerSlots, activeBarCount, createdCount, existingCount, skippedCount, localPlayer.playerClientId, startOfRound.inShipPhase, startOfRound.shipHasLanded, startOfRound.shipDoorsEnabled, startOfRound.shipIsLeaving, TimeOfDay.Instance?.currentDayTimeStarted, ((Object)(object)val != (Object)null) ? ((Object)val).name : "none")); } } } internal 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<RectTransform>(); if ((Object)(object)rectTransform == (Object)null) { rectTransform = ((Component)this).gameObject.AddComponent<RectTransform>(); } rectTransform.sizeDelta = new Vector2(172f, 20f); rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); GameObject val = new GameObject("Background", new Type[3] { typeof(RectTransform), typeof(Image), typeof(Outline) }); val.transform.SetParent(((Component)this).transform, false); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; backgroundImage = val.GetComponent<Image>(); ((Graphic)backgroundImage).raycastTarget = false; border = val.GetComponent<Outline>(); GameObject val2 = new GameObject("Fill", new Type[2] { typeof(RectTransform), typeof(Image) }); val2.transform.SetParent(val.transform, false); fillRect = val2.GetComponent<RectTransform>(); fillRect.anchorMin = Vector2.zero; fillRect.anchorMax = Vector2.one; fillRect.pivot = new Vector2(0f, 0.5f); fillRect.offsetMin = new Vector2(1f, 1f); fillRect.offsetMax = new Vector2(-1f, -1f); ((Transform)fillRect).localScale = Vector3.one; fillImage = val2.GetComponent<Image>(); ((Graphic)fillImage).raycastTarget = false; fillImage.type = (Type)0; GameObject val3 = new GameObject("Text", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val3.transform.SetParent(val.transform, false); RectTransform component2 = val3.GetComponent<RectTransform>(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = new Vector2(6f, 1f); component2.offsetMax = new Vector2(-6f, -1f); text = val3.GetComponent<TextMeshProUGUI>(); ((TMP_Text)text).alignment = (TextAlignmentOptions)514; ((TMP_Text)text).fontSize = 12f; ((TMP_Text)text).enableWordWrapping = false; ((Graphic)text).color = Color.white; ((Graphic)text).raycastTarget = false; } public void SetDisplay(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<Canvas>(); worldCanvas.renderMode = (RenderMode)2; worldCanvas.overrideSorting = true; worldCanvas.sortingOrder = 50; canvasRect = ((Component)worldCanvas).GetComponent<RectTransform>(); canvasRect.sizeDelta = new Vector2(220f, 84f); if (!TryCreateStrip("HealthStrip", out healthStrip)) { return false; } healthStripRect = ((Component)healthStrip).GetComponent<RectTransform>(); if (!TryCreateStrip("InfectionStrip", out infectionStrip)) { return false; } infectionStripRect = ((Component)infectionStrip).GetComponent<RectTransform>(); 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<PlayerStatusBarStrip>(); 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<PlayerStatusSnapshot>(); private static bool[] hasSnapshots = Array.Empty<bool>(); private static bool[] hasInfectionReports = Array.Empty<bool>(); private static ulong[] infectionReportActualClientIds = Array.Empty<ulong>(); private static bool[] infectionReportHasInfection = Array.Empty<bool>(); private static float[] infectionReportMeters = Array.Empty<float>(); private static float[] infectionReportReceivedTimes = Array.Empty<float>(); 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<byte>(ref b, default(ForPrimitives)); int position = ((FastBufferWriter)(ref val)).Position; ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(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<byte>(ref b5, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref actualClientId, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b6, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b3, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b4, default(ForPrimitives)); b2++; } int position2 = ((FastBufferWriter)(ref val)).Position; ((FastBufferWriter)(ref val)).Seek(position); ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(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<byte>(ref b3, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe<byte>(ref playerId, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe<ulong>(ref actualClientId, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe<byte>(ref b, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe<byte>(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<byte>(ref b, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(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<byte>(ref b3, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref actualClientId, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref health, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b4, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(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<byte>(ref b, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref reportedPlayerId, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b2, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(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<PlayerStatusBarManager>(); if ((Object)(object)runtimeManager == (Object)null) { runtimeManager = runtimeObject.AddComponent<PlayerStatusBarManager>(); } 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;