using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; 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: AssemblyCompany("TowerStatsMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.10.0")] [assembly: AssemblyInformationalVersion("1.0.10+ded0321878575e1e2dd3053142a71cc979c4a6c1")] [assembly: AssemblyProduct("TowerStatsMod")] [assembly: AssemblyTitle("TowerStatsMod")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.10.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TowerStatsMod { internal static class Patches { public static bool IsTower(Unit? unit) { //IL_0027: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 if ((Object)(object)unit == (Object)null || !unit.isBuilding) { return false; } if ((Object)(object)unit.playerBuilding != (Object)null) { HouseType houseType = unit.playerBuilding.HouseType; if ((int)houseType == 3 || (int)houseType == 9 || (int)houseType == 10 || (int)houseType == 17) { return true; } return false; } try { if ((Object)(object)((Component)unit).GetComponent() != (Object)null) { return true; } } catch { } return false; } public static TowerStatsComponent? GetOrCreateTowerStats(Unit unit, bool resetStats = false) { if ((Object)(object)unit == (Object)null || !IsTower(unit)) { return null; } TowerStatsComponent component = ((Component)unit).GetComponent(); if ((Object)(object)component != (Object)null) { if (resetStats) { component.ResetStats(); } return component; } try { TowerStatsComponent towerStatsComponent = ((Component)unit).gameObject.AddComponent(); towerStatsComponent.Init(unit); if (resetStats) { towerStatsComponent.ResetStats(); } return towerStatsComponent; } catch { return null; } } public static void EnsureComponentAttachedToBuilding(PlayerBuilding? building, bool resetStats = false) { if (!((Object)(object)building == (Object)null)) { Unit val = building.Owner; if ((Object)(object)val == (Object)null) { val = ((Component)building).GetComponent(); } if ((Object)(object)val == (Object)null) { val = ((Component)building).GetComponentInParent(); } if ((Object)(object)val != (Object)null && IsTower(val)) { GetOrCreateTowerStats(val, resetStats); } } } public static void EnsureComponentAttachedToSpot(BuildingSpot? spot, bool resetStats = false) { if (!((Object)(object)spot == (Object)null) && (Object)(object)spot.PlayerBuilding != (Object)null) { EnsureComponentAttachedToBuilding(spot.PlayerBuilding, resetStats); } } [HarmonyPatch(typeof(SimpleDamageable), "TakeDamage")] [HarmonyPostfix] private static void SimpleDamageable_TakeDamage_Postfix(SimpleDamageable __instance, float amount, IDamageSource source) { if (amount <= 0f || source == null) { return; } try { Unit sourceUnit = source.SourceUnit; if ((Object)(object)sourceUnit != (Object)null && IsTower(sourceUnit)) { TowerStatsComponent orCreateTowerStats = GetOrCreateTowerStats(sourceUnit); if ((Object)(object)orCreateTowerStats != (Object)null) { orCreateTowerStats.RecordDamage(amount); } } } catch { } } [HarmonyPatch(typeof(BuildingDamageable), "TakeDamage")] [HarmonyPostfix] private static void BuildingDamageable_TakeDamage_Postfix(BuildingDamageable __instance, float amount, IDamageSource source) { if (amount <= 0f || source == null) { return; } try { Unit sourceUnit = source.SourceUnit; if ((Object)(object)sourceUnit != (Object)null && IsTower(sourceUnit)) { TowerStatsComponent orCreateTowerStats = GetOrCreateTowerStats(sourceUnit); if ((Object)(object)orCreateTowerStats != (Object)null) { orCreateTowerStats.RecordDamage(amount); } } } catch { } } [HarmonyPatch(typeof(DamageBatchManager), "ApplyHitLocally")] [HarmonyPostfix] private static void DamageBatchManager_ApplyHitLocally_Postfix(ulong targetNetId, float damage, HitResultType type, bool isBuilding) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (damage <= 0f) { return; } try { if (!((Object)(object)NetworkManager.Singleton != (Object)null) || NetworkManager.Singleton.SpawnManager == null || !NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(targetNetId, out var value) || !((Object)(object)value != (Object)null)) { return; } Unit val = FindNearestTower(((Component)value).transform.position); if ((Object)(object)val != (Object)null) { TowerStatsComponent orCreateTowerStats = GetOrCreateTowerStats(val); if ((Object)(object)orCreateTowerStats != (Object)null) { orCreateTowerStats.RecordDamage(damage); } Unit component = ((Component)value).GetComponent(); if ((Object)(object)component != (Object)null && (component.IsDead || (component.Damageable != null && component.Damageable.CurrentHealth <= 0f))) { orCreateTowerStats?.RecordKill(); } } } catch { } } private static Unit? FindNearestTower(Vector3 targetPos) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) float num = 1600f; Unit result = null; IReadOnlyList activeTowers = TowerStatsManager.ActiveTowers; for (int i = 0; i < activeTowers.Count; i++) { TowerStatsComponent towerStatsComponent = activeTowers[i]; if ((Object)(object)towerStatsComponent == (Object)null) { continue; } Unit component = ((Component)towerStatsComponent).GetComponent(); if ((Object)(object)component != (Object)null && ((Component)component).gameObject.activeInHierarchy) { Vector3 val = ((Component)component).transform.position - targetPos; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = component; } } } return result; } [HarmonyPatch(typeof(PlayerStatisticsManager), "OnUnitKilled")] [HarmonyPostfix] private static void PlayerStatisticsManager_OnUnitKilled_Postfix(Unit victim, Unit killer) { if ((Object)(object)killer == (Object)null) { return; } try { if (IsTower(killer)) { TowerStatsComponent orCreateTowerStats = GetOrCreateTowerStats(killer); if ((Object)(object)orCreateTowerStats != (Object)null) { orCreateTowerStats.RecordKill(); } } } catch { } } [HarmonyPatch(typeof(PlayerBuilding), "OnNetworkSpawn")] [HarmonyPostfix] private static void PlayerBuilding_OnNetworkSpawn_Postfix(PlayerBuilding __instance) { EnsureComponentAttachedToBuilding(__instance, resetStats: true); } [HarmonyPatch(typeof(BuildingSpot), "OnEnable")] [HarmonyPostfix] private static void BuildingSpot_OnEnable_Postfix(BuildingSpot __instance) { EnsureComponentAttachedToSpot(__instance, resetStats: true); } [HarmonyPatch(typeof(BuildingDamageable), "Initialize")] [HarmonyPostfix] private static void BuildingDamageable_Initialize_Postfix(BuildingDamageable __instance, Unit owner) { if ((Object)(object)owner != (Object)null && IsTower(owner)) { GetOrCreateTowerStats(owner, resetStats: true); } } } [BepInPlugin("com.antigravity.towerstatsmod", "Tower Stats Mod", "1.0.10")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.antigravity.towerstatsmod"; public const string PluginName = "Tower Stats Mod"; public const string PluginVersion = "1.0.10"; private Harmony _harmony; public static Plugin Instance { get; private set; } = null; internal static ManualLogSource Log { get; private set; } = null; public static bool IsModEnabled { get; private set; } = true; public static ConfigEntry ToggleKey { get; private set; } = null; public static ConfigEntry DpsWindowSeconds { get; private set; } = null; public static ConfigEntry ShowRadius { get; private set; } = null; public static ConfigEntry HeightOffset { get; private set; } = null; public static ConfigEntry UiScale { get; private set; } = null; public static ConfigEntry FontSize { get; private set; } = null; public static ConfigEntry RenderThrough { get; private set; } = null; private void Awake() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfig(); _harmony = new Harmony("com.antigravity.towerstatsmod"); _harmony.PatchAll(typeof(Patches)); Log.LogInfo((object)"\ud83c\udff0 Tower Stats Mod v1.0.10 initialized! Press F6 to toggle stats display."); } private void Update() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = ToggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { IsModEnabled = !IsModEnabled; Log.LogInfo((object)("[TowerStatsMod] Tower stats display toggled " + (IsModEnabled ? "ON" : "OFF"))); } } private void BindConfig() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) ToggleKey = ((BaseUnityPlugin)this).Config.Bind("1 - General", "ToggleKey", new KeyboardShortcut((KeyCode)287, Array.Empty()), "Key to toggle tower stats display (F6)."); DpsWindowSeconds = ((BaseUnityPlugin)this).Config.Bind("1 - General", "DpsWindowSeconds", 5f, "Time window in seconds to calculate rolling current DPS."); ShowRadius = ((BaseUnityPlugin)this).Config.Bind("1 - General", "ShowRadius", 32f, "Maximum distance (in units) from player to show tower stats."); HeightOffset = ((BaseUnityPlugin)this).Config.Bind("2 - Appearance", "HeightOffset", 4f, "Height position offset above tower base."); UiScale = ((BaseUnityPlugin)this).Config.Bind("2 - Appearance", "UiScale", 0.019f, "Overall scale of the overhead badge."); FontSize = ((BaseUnityPlugin)this).Config.Bind("2 - Appearance", "FontSize", 16, "Font size of the kills and DPS text."); RenderThrough = ((BaseUnityPlugin)this).Config.Bind("2 - Appearance", "RenderThrough", true, "Render text through/on top of tower geometry (ZTest Always)."); } private void OnDestroy() { _harmony.UnpatchSelf(); } } public sealed class TowerStatsComponent : MonoBehaviour { private struct DamageSample { public float Time; public float Amount; } private Unit? _unit; private readonly Queue _rollingSamples = new Queue(128); private Canvas? _canvas; private RectTransform? _canvasRT; private Text? _killsText; private Text? _dpsText; private Image? _bgImage; private Camera? _cam; private static Material? _alwaysOnTopTextMaterial; private static Material? _alwaysOnTopImageMaterial; private static PlayerInteract? _staticLocalPlayer; private static float _staticLastPlayerCheckTime; private bool _uiInitialized; private float _nextUIUpdate; private float _lastRenderedDPS = -1f; private int _lastRenderedKills = -1; public float TotalDamage { get; private set; } public int Kills { get; private set; } public float CurrentDPS { get; private set; } public static Material AlwaysOnTopTextMaterial { get { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown if ((Object)(object)_alwaysOnTopTextMaterial == (Object)null) { try { Shader val = Shader.Find("GUI/Text Shader") ?? Shader.Find("UI/Default"); if ((Object)(object)val != (Object)null) { _alwaysOnTopTextMaterial = new Material(val); ((Object)_alwaysOnTopTextMaterial).name = "TowerStatsTextOverlayMaterial"; _alwaysOnTopTextMaterial.SetInt("unity_GUIZTestMode", 8); _alwaysOnTopTextMaterial.SetInt("_ZTest", 8); _alwaysOnTopTextMaterial.renderQueue = 3000; } } catch { } } return _alwaysOnTopTextMaterial; } } public static Material AlwaysOnTopImageMaterial { get { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown if ((Object)(object)_alwaysOnTopImageMaterial == (Object)null) { try { Shader val = Shader.Find("UI/Default") ?? Shader.Find("Sprites/Default"); if ((Object)(object)val != (Object)null) { _alwaysOnTopImageMaterial = new Material(val); ((Object)_alwaysOnTopImageMaterial).name = "TowerStatsImageOverlayMaterial"; _alwaysOnTopImageMaterial.SetInt("unity_GUIZTestMode", 8); _alwaysOnTopImageMaterial.SetInt("_ZTest", 8); _alwaysOnTopImageMaterial.renderQueue = 3000; } } catch { } } return _alwaysOnTopImageMaterial; } } public void Init(Unit unit) { _unit = unit; } public void ResetStats() { TotalDamage = 0f; Kills = 0; CurrentDPS = 0f; _rollingSamples.Clear(); _lastRenderedDPS = -1f; _lastRenderedKills = -1; UpdateUIContent(); } private void Start() { BuildUI(); _uiInitialized = true; } private void OnEnable() { if (_uiInitialized && ((Object)(object)_canvas == (Object)null || (Object)(object)_killsText == (Object)null)) { BuildUI(); } } private void OnDestroy() { if ((Object)(object)_canvas != (Object)null && (Object)(object)((Component)_canvas).gameObject != (Object)null) { Object.Destroy((Object)(object)((Component)_canvas).gameObject); } } public void RecordDamage(float amount) { if (!(amount <= 0f)) { TotalDamage += amount; _rollingSamples.Enqueue(new DamageSample { Time = Time.time, Amount = amount }); } } public void RecordKill(int count = 1) { if (count > 0) { Kills += count; } } private void Update() { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0179: 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) if (!Plugin.IsModEnabled) { return; } if ((Object)(object)_canvas == (Object)null || (Object)(object)_canvasRT == (Object)null || (Object)(object)_killsText == (Object)null || (Object)(object)_dpsText == (Object)null) { BuildUI(); if ((Object)(object)_canvas == (Object)null || (Object)(object)_canvasRT == (Object)null) { return; } } PlayerInteract localPlayer = GetLocalPlayer(); float value = Plugin.ShowRadius.Value; if ((Object)(object)localPlayer != (Object)null && (Object)(object)((Component)localPlayer).gameObject != (Object)null && ((Component)localPlayer).gameObject.activeInHierarchy && value > 0f && Vector3.Distance(((Component)this).transform.position, ((Component)localPlayer).transform.position) > value) { if (((Component)_canvas).gameObject.activeSelf) { ((Component)_canvas).gameObject.SetActive(false); } return; } if (!((Component)_canvas).gameObject.activeSelf) { ((Component)_canvas).gameObject.SetActive(true); } if ((Object)(object)_cam == (Object)null || !((Behaviour)_cam).isActiveAndEnabled) { _cam = Camera.main; } if ((Object)(object)_cam != (Object)null) { Vector3 val = ((Component)_canvas).transform.position - ((Component)_cam).transform.position; if (val != Vector3.zero) { ((Component)_canvas).transform.rotation = Quaternion.LookRotation(val); } } float value2 = Plugin.DpsWindowSeconds.Value; float num = Time.time - value2; while (_rollingSamples.Count > 0 && _rollingSamples.Peek().Time < num) { _rollingSamples.Dequeue(); } float num2 = 0f; foreach (DamageSample rollingSample in _rollingSamples) { num2 += rollingSample.Amount; } CurrentDPS = ((value2 > 0.1f) ? (num2 / value2) : 0f); if (Time.time >= _nextUIUpdate) { _nextUIUpdate = Time.time + 0.1f; UpdateUIContent(); } } private void BuildUI() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0092: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_019b: 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_01cc: 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) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Expected O, but got Unknown //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) Transform val = ((Component)this).transform.Find("TowerStatsCanvas"); if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)((Component)val).gameObject); } GameObject val2 = new GameObject("TowerStatsCanvas"); val2.transform.SetParent(((Component)this).transform, false); _canvas = val2.AddComponent(); _canvas.renderMode = (RenderMode)2; _canvas.overrideSorting = true; _canvas.sortingOrder = 30000; _canvasRT = val2.GetComponent(); _canvasRT.sizeDelta = new Vector2(100f, 52f); ((Transform)_canvasRT).localScale = Vector3.one * Plugin.UiScale.Value; ((Transform)_canvasRT).localPosition = new Vector3(0f, Plugin.HeightOffset.Value, 0f); _bgImage = val2.AddComponent(); ((Graphic)_bgImage).color = new Color(0.04f, 0.06f, 0.1f, 0.9f); Font builtinResource = Resources.GetBuiltinResource("Arial.ttf"); int value = Plugin.FontSize.Value; GameObject val3 = new GameObject("KillsLabel"); val3.transform.SetParent(val2.transform, false); _killsText = val3.AddComponent(); _killsText.font = builtinResource; _killsText.fontSize = value; _killsText.fontStyle = (FontStyle)1; _killsText.alignment = (TextAnchor)4; ((Graphic)_killsText).color = new Color(0.95f, 0.95f, 0.95f, 1f); RectTransform component = val3.GetComponent(); component.anchorMin = new Vector2(0f, 0.5f); component.anchorMax = new Vector2(1f, 1f); component.offsetMin = new Vector2(2f, 0f); component.offsetMax = new Vector2(-2f, 0f); GameObject val4 = new GameObject("DPSLabel"); val4.transform.SetParent(val2.transform, false); _dpsText = val4.AddComponent(); _dpsText.font = builtinResource; _dpsText.fontSize = value; _dpsText.fontStyle = (FontStyle)1; _dpsText.alignment = (TextAnchor)4; ((Graphic)_dpsText).color = new Color(0.35f, 0.85f, 1f, 1f); RectTransform component2 = val4.GetComponent(); component2.anchorMin = new Vector2(0f, 0f); component2.anchorMax = new Vector2(1f, 0.5f); component2.offsetMin = new Vector2(2f, 0f); component2.offsetMax = new Vector2(-2f, 0f); ApplyOverlayMaterials(); UpdateUIContent(); } private void ApplyOverlayMaterials() { if (!Plugin.RenderThrough.Value) { return; } Material alwaysOnTopImageMaterial = AlwaysOnTopImageMaterial; if ((Object)(object)alwaysOnTopImageMaterial != (Object)null && (Object)(object)_bgImage != (Object)null) { ((Graphic)_bgImage).material = alwaysOnTopImageMaterial; } Material alwaysOnTopTextMaterial = AlwaysOnTopTextMaterial; if ((Object)(object)alwaysOnTopTextMaterial != (Object)null) { if ((Object)(object)_killsText != (Object)null) { ((Graphic)_killsText).material = alwaysOnTopTextMaterial; } if ((Object)(object)_dpsText != (Object)null) { ((Graphic)_dpsText).material = alwaysOnTopTextMaterial; } } } private void UpdateUIContent() { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_killsText == (Object)null) && !((Object)(object)_dpsText == (Object)null) && !((Object)(object)_canvasRT == (Object)null) && (!(Mathf.Abs(CurrentDPS - _lastRenderedDPS) < 0.1f) || Kills != _lastRenderedKills)) { _lastRenderedDPS = CurrentDPS; _lastRenderedKills = Kills; int value = Plugin.FontSize.Value; if (_killsText.fontSize != value) { _killsText.fontSize = value; } if (_dpsText.fontSize != value) { _dpsText.fontSize = value; } ApplyOverlayMaterials(); string text = FormatNumber(CurrentDPS); string text2 = Kills.ToString(CultureInfo.InvariantCulture); _killsText.text = "⚔ Kills: " + text2; _dpsText.text = "⚡ DPS: " + text; float num = Mathf.Clamp(Mathf.Max(_killsText.preferredWidth, _dpsText.preferredWidth) + 20f, 75f, 320f); if (Mathf.Abs(_canvasRT.sizeDelta.x - num) > 1f) { _canvasRT.sizeDelta = new Vector2(num, 52f); } } } public static string FormatNumber(float val) { if (val < 0.1f) { return "0"; } if (val < 1000f) { return val.ToString("F0", CultureInfo.InvariantCulture); } if (val < 1000000f) { return (val / 1000f).ToString("F1", CultureInfo.InvariantCulture) + "k"; } return (val / 1000000f).ToString("F2", CultureInfo.InvariantCulture) + "M"; } private static PlayerInteract? GetLocalPlayer() { if ((Object)(object)_staticLocalPlayer != (Object)null && (Object)(object)((Component)_staticLocalPlayer).gameObject != (Object)null && ((Component)_staticLocalPlayer).gameObject.activeInHierarchy) { return _staticLocalPlayer; } if (Time.time - _staticLastPlayerCheckTime < 0.5f) { return _staticLocalPlayer; } _staticLastPlayerCheckTime = Time.time; try { PlayerInteract[] array = Object.FindObjectsOfType(); if (array != null) { PlayerInteract[] array2 = array; foreach (PlayerInteract val in array2) { if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && ((NetworkBehaviour)val).IsOwner) { _staticLocalPlayer = val; return _staticLocalPlayer; } } } } catch { } return _staticLocalPlayer; } } public static class TowerStatsManager { private static readonly List _activeTowers = new List(); public static IReadOnlyList ActiveTowers => _activeTowers; public static void RegisterTower(TowerStatsComponent tower) { if ((Object)(object)tower != (Object)null && !_activeTowers.Contains(tower)) { _activeTowers.Add(tower); } } public static void UnregisterTower(TowerStatsComponent tower) { if ((Object)(object)tower != (Object)null) { _activeTowers.Remove(tower); } } public static void Clear() { _activeTowers.Clear(); } } }