using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TeamPing.Config; using TeamPing.Core; using TeamPing.Network; using TeamPing.UI; using TeamPing.Utils; using UnityEngine; using UnityEngine.SceneManagement; 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("TeamPing")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyProduct("TeamPing")] [assembly: AssemblyTitle("TeamPing")] [assembly: AssemblyVersion("1.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TeamPing { [BepInPlugin("com.juanma.teamping", "TeamPing", "1.0.1")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.juanma.teamping"; public const string PluginName = "TeamPing"; public const string PluginVersion = "1.0.1"; private static Plugin? _activeInstance; private ModConfig? _modConfig; private GameBindings? _bindings; private PingController? _pingController; private Harmony? _harmony; private void Awake() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown Scene activeScene = SceneManager.GetActiveScene(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeamPing] Loaded v1.0.1"); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Lifecycle] Awake: unity={1}, gameVersion={2}, scene={3}[{4}], config={5}", "TeamPing", Application.unityVersion, Application.version, ((Scene)(ref activeScene)).name, ((Scene)(ref activeScene)).buildIndex, ((BaseUnityPlugin)this).Config.ConfigFilePath)); _modConfig = new ModConfig(((BaseUnityPlugin)this).Config); _bindings = new GameBindings(((BaseUnityPlugin)this).Logger); if (_bindings.Resolve()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeamPing] Game bindings resolved."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[TeamPing] Some optional bindings are unavailable; affected metadata will be hidden safely."); } _pingController = new PingController(_modConfig, _bindings, ((BaseUnityPlugin)this).Logger); _activeInstance = this; _harmony = new Harmony("com.juanma.teamping"); MethodInfo methodInfo = AccessTools.Method(typeof(Plugin), "GameUpdatePostfix", (Type[])null, (Type[])null); int num = 0; if (methodInfo != null) { num += (InstallUpdateHook(typeof(GameDirector), methodInfo) ? 1 : 0); num += (InstallUpdateHook(typeof(RunManager), methodInfo) ? 1 : 0); } ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Lifecycle] Safe update hooks installed: {1}/2. Plugin Update remains as a guarded fallback.", "TeamPing", num)); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeamPing] Ready. Press the middle mouse button while aiming at an object or surface."); } private void Update() { _pingController?.Tick(Time.unscaledDeltaTime); } private static void GameUpdatePostfix() { _activeInstance?._pingController?.Tick(Time.unscaledDeltaTime); } private bool InstallUpdateHook(Type gameType, MethodInfo postfix) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(gameType, "Update", (Type[])null, (Type[])null); if (methodInfo == null || _harmony == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[TeamPing/Lifecycle] Update hook unavailable for " + gameType.Name + ".")); return false; } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Scene] Loaded: {1}[{2}], mode={3}, frame={4}", "TeamPing", ((Scene)(ref scene)).name, ((Scene)(ref scene)).buildIndex, mode, Time.frameCount)); _pingController?.HandleSceneChanged(); } private void OnActiveSceneChanged(Scene previous, Scene current) { ModConfig? modConfig = _modConfig; if (modConfig != null && modConfig.DebugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[TeamPing/Scene] Active scene: " + ((Scene)(ref previous)).name + " -> " + ((Scene)(ref current)).name)); } } private void OnDestroy() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeamPing/Lifecycle] Shutdown started."); SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.activeSceneChanged -= OnActiveSceneChanged; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)_activeInstance == (Object)(object)this) { _activeInstance = null; } _pingController?.Dispose(); _modConfig?.Dispose(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeamPing/Lifecycle] Shutdown complete."); } } } namespace TeamPing.Utils { internal static class TextureUtils { public static Sprite CreateRoundedRectSprite(string name, int width, int height, int radius, int borderThickness = 0) { //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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(width, height, (TextureFormat)4, false) { name = name + "_Texture", filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; Color32[] array = (Color32[])(object)new Color32[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { bool flag = InsideRoundedRect((float)j + 0.5f, (float)i + 0.5f, width, height, radius, 0); bool flag2 = borderThickness > 0 && InsideRoundedRect((float)j + 0.5f, (float)i + 0.5f, width, height, Mathf.Max(0, radius - borderThickness), borderThickness); array[i * width + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)((flag && !flag2) ? byte.MaxValue : 0)); } } val.SetPixels32(array); val.Apply(false, true); Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)width, (float)height), new Vector2(0.5f, 0.5f), 100f); ((Object)obj).name = name; ((Object)obj).hideFlags = (HideFlags)61; return obj; } private static bool InsideRoundedRect(float x, float y, int width, int height, int radius, int inset) { int num = width - inset; int num2 = height - inset; if (x < (float)inset || x > (float)num || y < (float)inset || y > (float)num2) { return false; } if (radius <= 0) { return true; } float num3 = Mathf.Clamp(x, (float)(inset + radius), (float)(num - radius)); float num4 = Mathf.Clamp(y, (float)(inset + radius), (float)(num2 - radius)); float num5 = x - num3; float num6 = y - num4; return num5 * num5 + num6 * num6 <= (float)(radius * radius); } } } namespace TeamPing.UI { public sealed class PingHudController : IDisposable { private readonly ModConfig _config; private readonly ManualLogSource _logger; private readonly PingStyles _styles; private readonly List _markers = new List(); private GameObject? _canvasRoot; private RectTransform? _canvasRect; private AudioSource? _audioSource; public PingHudController(ModConfig config, ManualLogSource logger) { _config = config; _logger = logger; _styles = new PingStyles(config); } public void Show(PingData ping) { EnsureCanvas(); if (!((Object)(object)_canvasRect == (Object)null)) { int num = Mathf.Clamp(_config.MaxPingsPerPlayer.Value, 1, 8); while (CountForSender(ping.SenderActorNumber) >= num) { RemoveOldestForSender(ping.SenderActorNumber); } PingMarker item = new PingMarker(ping, (Transform)(object)_canvasRect, _config, _styles); _markers.Add(item); PlaySound(); } } public void Tick(float unscaledDeltaTime, Camera? camera, bool gameplayActive) { if ((Object)(object)_canvasRoot == (Object)null) { if (_markers.Count > 0) { ClearMarkersOnly(); } return; } _canvasRoot.SetActive(gameplayActive && _config.Enabled.Value); if (!gameplayActive || (Object)(object)camera == (Object)null || (Object)(object)_canvasRect == (Object)null) { return; } for (int num = _markers.Count - 1; num >= 0; num--) { if (!_markers[num].Tick(unscaledDeltaTime, camera, _canvasRect)) { _markers[num].Dispose(); _markers.RemoveAt(num); } } } public void ApplyConfig() { _styles.Refresh(); foreach (PingMarker marker in _markers) { marker.ApplyConfig(); } if ((Object)(object)_audioSource != (Object)null) { _audioSource.volume = Mathf.Clamp01(_config.SoundVolume.Value); } } public void Clear() { ClearMarkersOnly(); } private void EnsureCanvas() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_0109: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_canvasRoot != (Object)null) || !((Object)(object)_canvasRect != (Object)null)) { ClearMarkersOnly(); _canvasRoot = new GameObject("TeamPing_Canvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(AudioSource) }); ((Object)_canvasRoot).hideFlags = (HideFlags)61; _canvasRect = (RectTransform)_canvasRoot.transform; _canvasRect.anchorMin = Vector2.zero; _canvasRect.anchorMax = Vector2.one; _canvasRect.offsetMin = Vector2.zero; _canvasRect.offsetMax = Vector2.zero; Canvas component = _canvasRoot.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 32000; CanvasScaler component2 = _canvasRoot.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; _audioSource = _canvasRoot.GetComponent(); _audioSource.playOnAwake = false; _audioSource.loop = false; _audioSource.spatialBlend = 0f; _audioSource.ignoreListenerPause = true; _audioSource.clip = _styles.PingClip; _audioSource.volume = Mathf.Clamp01(_config.SoundVolume.Value); _logger.LogInfo((object)"[TeamPing/UI] Ping canvas created."); } } private int CountForSender(int senderActorNumber) { int num = 0; foreach (PingMarker marker in _markers) { if (marker.SenderActorNumber == senderActorNumber) { num++; } } return num; } private void RemoveOldestForSender(int senderActorNumber) { int num = -1; float num2 = float.PositiveInfinity; for (int i = 0; i < _markers.Count; i++) { if (_markers[i].SenderActorNumber == senderActorNumber && !(_markers[i].CreatedAt >= num2)) { num2 = _markers[i].CreatedAt; num = i; } } if (num >= 0) { _markers[num].Dispose(); _markers.RemoveAt(num); } } private void PlaySound() { if (_config.SoundEnabled.Value && !((Object)(object)_audioSource == (Object)null) && !((Object)(object)_styles.PingClip == (Object)null)) { _audioSource.volume = Mathf.Clamp01(_config.SoundVolume.Value); _audioSource.PlayOneShot(_styles.PingClip, 1f); } } private void ClearMarkersOnly() { foreach (PingMarker marker in _markers) { marker.Dispose(); } _markers.Clear(); } public void Dispose() { ClearMarkersOnly(); if ((Object)(object)_canvasRoot != (Object)null) { Object.Destroy((Object)(object)_canvasRoot); } _canvasRoot = null; _canvasRect = null; _audioSource = null; _styles.Dispose(); } } public sealed class PingMarker : IDisposable { private readonly PingData _data; private readonly ModConfig _config; private readonly PingStyles _styles; private readonly GameObject _root; private readonly RectTransform _rootRect; private readonly CanvasGroup _canvasGroup; private readonly Image _diamond; private readonly Shadow _diamondGlow; private readonly Image _dot; private readonly Image _panel; private readonly Image _accent; private readonly Text _labelText; private readonly Text _detailsText; private readonly Text _edgeArrow; private readonly float _expiresAt; private Transform? _targetTransform; private Vector3 _targetLocalOffset; private Vector3 _lastWorldPosition; private float _nextTargetLookup; private bool _disposed; public int SenderActorNumber => _data.SenderActorNumber; public float CreatedAt { get; } = Time.unscaledTime; public PingMarker(PingData data, Transform parent, ModConfig config, PingStyles styles) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0196: 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_01b5: Expected O, but got Unknown //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Expected O, but got Unknown //IL_0275: 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_030a: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Expected O, but got Unknown //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Expected O, but got Unknown //IL_041f: Unknown result type (might be due to invalid IL or missing references) //IL_0434: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_0480: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_04aa: Unknown result type (might be due to invalid IL or missing references) //IL_04b5: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_04c0: Unknown result type (might be due to invalid IL or missing references) //IL_04d4: Unknown result type (might be due to invalid IL or missing references) //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Expected O, but got Unknown //IL_05b9: Unknown result type (might be due to invalid IL or missing references) //IL_05cd: Unknown result type (might be due to invalid IL or missing references) //IL_0610: Unknown result type (might be due to invalid IL or missing references) //IL_062e: Expected O, but got Unknown //IL_0696: Unknown result type (might be due to invalid IL or missing references) //IL_06b4: Expected O, but got Unknown //IL_06fd: Unknown result type (might be due to invalid IL or missing references) //IL_0711: Unknown result type (might be due to invalid IL or missing references) _data = data; _config = config; _styles = styles; _lastWorldPosition = data.WorldPosition; _expiresAt = CreatedAt + Mathf.Clamp(config.Duration.Value, 1f, 30f); _root = CreateUiObject("Ping_" + data.SenderActorNumber + "_" + data.PingId, parent, typeof(CanvasGroup)); _rootRect = (RectTransform)_root.transform; _rootRect.anchorMin = new Vector2(0.5f, 0.5f); _rootRect.anchorMax = new Vector2(0.5f, 0.5f); _rootRect.pivot = new Vector2(0.5f, 0.5f); _rootRect.sizeDelta = new Vector2(240f, 104f); _canvasGroup = _root.GetComponent(); _canvasGroup.interactable = false; _canvasGroup.blocksRaycasts = false; GameObject val = CreateUiObject("Target", _root.transform, typeof(CanvasRenderer), typeof(Image), typeof(Shadow)); RectTransform val2 = (RectTransform)val.transform; SetCentered(val2, 0f, 23f, 38f, 38f); ((Transform)val2).localRotation = Quaternion.Euler(0f, 0f, 45f); _diamond = val.GetComponent(); _diamond.sprite = styles.DiamondBorderSprite; ((Graphic)_diamond).raycastTarget = false; _diamondGlow = val.GetComponent(); _diamondGlow.effectDistance = new Vector2(2f, -2f); _diamondGlow.useGraphicAlpha = true; GameObject obj = CreateUiObject("ChromaticGhost", val.transform, typeof(CanvasRenderer), typeof(Image)); RectTransform val3 = (RectTransform)obj.transform; Stretch(val3); val3.anchoredPosition = new Vector2(-2f, 1f); Image component = obj.GetComponent(); component.sprite = styles.DiamondBorderSprite; ((Graphic)component).color = new Color(0.08f, 0.55f, 1f, 0.3f); ((Graphic)component).raycastTarget = false; ((Component)component).transform.SetAsFirstSibling(); GameObject val4 = CreateUiObject("Dot", _root.transform, typeof(CanvasRenderer), typeof(Image), typeof(Shadow)); SetCentered((RectTransform)val4.transform, 0f, 23f, 10f, 10f); _dot = val4.GetComponent(); _dot.sprite = styles.DotSprite; ((Graphic)_dot).raycastTarget = false; Shadow component2 = val4.GetComponent(); component2.effectDistance = new Vector2(1f, -1f); component2.useGraphicAlpha = true; GameObject val5 = CreateUiObject("Info", _root.transform, typeof(CanvasRenderer), typeof(Image), typeof(Shadow)); SetCentered((RectTransform)val5.transform, 0f, -28f, 220f, 46f); _panel = val5.GetComponent(); _panel.sprite = styles.PanelSprite; ((Graphic)_panel).raycastTarget = false; Shadow component3 = val5.GetComponent(); component3.effectColor = new Color(0f, 0f, 0f, 0.82f); component3.effectDistance = new Vector2(2f, -2f); component3.useGraphicAlpha = true; GameObject val6 = CreateUiObject("Accent", val5.transform, typeof(CanvasRenderer), typeof(Image)); RectTransform val7 = (RectTransform)val6.transform; val7.anchorMin = new Vector2(0f, 0f); val7.anchorMax = new Vector2(0f, 1f); val7.pivot = new Vector2(0f, 0.5f); val7.anchoredPosition = Vector2.zero; val7.sizeDelta = new Vector2(3f, 0f); _accent = val6.GetComponent(); ((Graphic)_accent).raycastTarget = false; GameObject val8 = CreateUiObject("Label", val5.transform, typeof(CanvasRenderer), typeof(Text), typeof(Shadow)); SetTopLeft((RectTransform)val8.transform, 11f, 2f, 198f, 24f); _labelText = val8.GetComponent(); ConfigureText(_labelText, 16, (FontStyle)1, (TextAnchor)3); _labelText.resizeTextForBestFit = true; _labelText.resizeTextMinSize = 10; _labelText.resizeTextMaxSize = 16; Shadow component4 = val8.GetComponent(); component4.effectColor = new Color(0f, 0f, 0f, 0.9f); component4.effectDistance = new Vector2(1f, -1f); GameObject val9 = CreateUiObject("Details", val5.transform, typeof(CanvasRenderer), typeof(Text)); SetTopLeft((RectTransform)val9.transform, 11f, 24f, 198f, 18f); _detailsText = val9.GetComponent(); ConfigureText(_detailsText, 11, (FontStyle)1, (TextAnchor)3); GameObject val10 = CreateUiObject("EdgeArrow", _root.transform, typeof(CanvasRenderer), typeof(Text), typeof(Shadow)); SetCentered((RectTransform)val10.transform, 0f, 49f, 30f, 24f); _edgeArrow = val10.GetComponent(); ConfigureText(_edgeArrow, 21, (FontStyle)1, (TextAnchor)4); _edgeArrow.text = "▲"; Shadow component5 = val10.GetComponent(); component5.effectColor = new Color(0f, 0f, 0f, 0.85f); component5.effectDistance = new Vector2(1f, -1f); ((Component)_edgeArrow).gameObject.SetActive(false); ResolveTargetTransform(); ApplyConfig(); } public bool Tick(float unscaledDeltaTime, Camera camera, RectTransform canvasRect) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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_0111: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0151: 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) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: 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_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0362: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_03b0: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) if (_disposed || (Object)(object)_root == (Object)null) { return false; } float unscaledTime = Time.unscaledTime; float num = _expiresAt - unscaledTime; if (num <= 0f) { return false; } if (_data.ViewId > 0 && (Object)(object)_targetTransform == (Object)null && unscaledTime >= _nextTargetLookup) { _nextTargetLookup = unscaledTime + 0.5f; ResolveTargetTransform(); } if ((Object)(object)_targetTransform != (Object)null) { _lastWorldPosition = _targetTransform.TransformPoint(_targetLocalOffset); } Vector3 val = _lastWorldPosition; if (_data.Kind == PingTargetKind.Location) { Vector3 surfaceNormal = _data.SurfaceNormal; if (((Vector3)(ref surfaceNormal)).sqrMagnitude > 0.01f) { Vector3 val2 = val; surfaceNormal = _data.SurfaceNormal; val = val2 + ((Vector3)(ref surfaceNormal)).normalized * 0.06f; } } Vector3 val3 = camera.WorldToViewportPoint(val); Vector3 val4 = default(Vector3); ((Vector3)(ref val4))..ctor(val3.x * (float)Screen.width, val3.y * (float)Screen.height, val3.z); bool num2 = val4.z <= 0f; if (num2) { val4.x = (float)Screen.width - val4.x; val4.y = (float)Screen.height - val4.y; } Vector2 val5 = default(Vector2); ((Vector2)(ref val5))..ctor(val4.x, val4.y); float num3 = Mathf.Clamp(_config.MarkerScale.Value, 0.5f, 2f); float num4 = 125f * num3; float num5 = 78f * num3; bool flag = num2 || val4.x < num4 || val4.x > (float)Screen.width - num4 || val4.y < num5 || val4.y > (float)Screen.height - num5; if (flag && !_config.ScreenEdgeIndicators.Value) { _root.SetActive(false); return true; } _root.SetActive(true); val4.x = Mathf.Clamp(val4.x, num4, (float)Screen.width - num4); val4.y = Mathf.Clamp(val4.y, num5, (float)Screen.height - num5); Vector2 anchoredPosition = default(Vector2); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, Vector2.op_Implicit(val4), (Camera)null, ref anchoredPosition)) { _rootRect.anchoredPosition = anchoredPosition; } ((Component)_edgeArrow).gameObject.SetActive(flag); if (flag) { Vector2 val6 = val5 - new Vector2((float)Screen.width * 0.5f, (float)Screen.height * 0.5f); if (((Vector2)(ref val6)).sqrMagnitude < 0.001f) { val6 = Vector2.up; } float num6 = Mathf.Atan2(val6.y, val6.x) * 57.29578f - 90f; ((Transform)((Graphic)_edgeArrow).rectTransform).localRotation = Quaternion.Euler(0f, 0f, num6); } float num7 = unscaledTime - CreatedAt; float num8 = Mathf.Clamp01(num7 / 0.14f); float num9 = Mathf.Clamp01(num / 0.55f); _canvasGroup.alpha = Mathf.Min(num8, num9); float num10 = 1f + Mathf.Sin(num7 * 7.5f) * 0.035f; ((Transform)_rootRect).localScale = Vector3.one * num3 * num10; ((Transform)((Graphic)_diamond).rectTransform).localRotation = Quaternion.Euler(0f, 0f, 45f + Mathf.Sin(num7 * 2.8f) * 2.5f); UpdateDetails(Vector3.Distance(((Component)camera).transform.position, val)); return true; } public void ApplyConfig() { //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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007a: 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_00a6: Unknown result type (might be due to invalid IL or missing references) Color val = _styles.ColorFor(_data.Kind); ((Graphic)_diamond).color = val; _diamondGlow.effectColor = new Color(val.r, val.g, val.b, 0.55f); ((Graphic)_dot).color = val; ((Graphic)_accent).color = val; ((Graphic)_edgeArrow).color = val; ((Graphic)_labelText).color = _styles.TextColor; ((Graphic)_detailsText).color = _styles.MutedTextColor; ((Graphic)_panel).color = _styles.PanelColor; _labelText.text = (string.IsNullOrWhiteSpace(_data.Label) ? "PING" : _data.Label.Trim().ToUpperInvariant()); UpdateDetails(0f); } private void UpdateDetails(float distance) { List list = new List(3); if (_config.ShowSender.Value && !string.IsNullOrWhiteSpace(_data.SenderName)) { list.Add(_data.SenderName.Trim().ToUpperInvariant()); } if (_data.HasValue) { list.Add("$" + _data.Value.ToString("N0", CultureInfo.InvariantCulture)); } if (_config.ShowDistance.Value && distance > 0f) { list.Add(Mathf.RoundToInt(distance) + " M"); } _detailsText.text = string.Join(" / ", list); } private void ResolveTargetTransform() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if (_data.ViewId <= 0) { return; } try { PhotonView val = PhotonView.Find(_data.ViewId); if (!((Object)(object)val == (Object)null)) { _targetTransform = ((Component)val).transform; _targetLocalOffset = _targetTransform.InverseTransformPoint(_lastWorldPosition); } } catch { _targetTransform = null; } } public void Dispose() { if (!_disposed) { _disposed = true; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _targetTransform = null; } } private void ConfigureText(Text text, int size, FontStyle style, TextAnchor alignment) { //IL_0019: 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) text.font = _styles.Font; text.fontSize = size; text.fontStyle = style; text.alignment = alignment; text.horizontalOverflow = (HorizontalWrapMode)1; text.verticalOverflow = (VerticalWrapMode)0; text.supportRichText = false; ((Graphic)text).raycastTarget = false; } private static GameObject CreateUiObject(string name, Transform parent, params Type[] components) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown Type[] array = new Type[components.Length + 1]; array[0] = typeof(RectTransform); components.CopyTo(array, 1); GameObject val = new GameObject(name, array); val.transform.SetParent(parent, false); return val; } private static void SetCentered(RectTransform rect, float x, float y, float width, float height) { //IL_000b: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = new Vector2(x, y); rect.sizeDelta = new Vector2(width, height); } private static void SetTopLeft(RectTransform rect, float x, float y, float width, float height) { //IL_000b: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0f, 1f); rect.anchorMax = new Vector2(0f, 1f); rect.pivot = new Vector2(0f, 1f); rect.anchoredPosition = new Vector2(x, 0f - y); rect.sizeDelta = new Vector2(width, height); } private static void Stretch(RectTransform rect) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } } public sealed class PingStyles { private readonly ModConfig _config; private readonly List _sprites = new List(); private readonly bool _ownsFont; public Font Font { get; } public Sprite PanelSprite { get; } public Sprite DiamondBorderSprite { get; } public Sprite DotSprite { get; } public AudioClip PingClip { get; } public Color ValuableColor { get; private set; } public Color ItemColor { get; private set; } public Color CartColor { get; private set; } public Color PlayerColor { get; private set; } public Color LocationColor { get; private set; } public Color PanelColor { get; } = new Color(0.008f, 0.014f, 0.018f, 0.82f); public Color TextColor { get; } = new Color(0.95f, 1f, 0.96f, 1f); public Color MutedTextColor { get; } = new Color(0.72f, 0.78f, 0.75f, 1f); public PingStyles(ModConfig config) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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) _config = config; Font = Font.CreateDynamicFontFromOSFont(new string[4] { "Bahnschrift SemiCondensed", "Arial Narrow", "Segoe UI Semibold", "Arial" }, 18); if ((Object)(object)Font != (Object)null) { _ownsFont = true; } else { Font = Resources.GetBuiltinResource("Arial.ttf"); } PanelSprite = Add(TextureUtils.CreateRoundedRectSprite("TeamPing_Panel", 220, 46, 3)); DiamondBorderSprite = Add(TextureUtils.CreateRoundedRectSprite("TeamPing_Diamond", 42, 42, 4, 3)); DotSprite = Add(TextureUtils.CreateRoundedRectSprite("TeamPing_Dot", 16, 16, 8)); PingClip = CreatePingClip(); Refresh(); } public void Refresh() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) ValuableColor = Parse(_config.ValuableColor.Value, new Color(0.38f, 1f, 0.53f, 1f)); ItemColor = Parse(_config.ItemColor.Value, new Color(0.33f, 0.85f, 1f, 1f)); CartColor = Parse(_config.CartColor.Value, new Color(1f, 0.68f, 0.26f, 1f)); PlayerColor = Parse(_config.PlayerColor.Value, new Color(0.84f, 0.54f, 1f, 1f)); LocationColor = Parse(_config.LocationColor.Value, new Color(1f, 0.95f, 0.66f, 1f)); } public Color ColorFor(PingTargetKind kind) { //IL_001f: 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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) return (Color)(kind switch { PingTargetKind.Valuable => ValuableColor, PingTargetKind.Item => ItemColor, PingTargetKind.Cart => CartColor, PingTargetKind.Player => PlayerColor, PingTargetKind.Object => ItemColor, _ => LocationColor, }); } public void Dispose() { foreach (Sprite sprite in _sprites) { if (!((Object)(object)sprite == (Object)null)) { Texture2D texture = sprite.texture; Object.Destroy((Object)(object)sprite); if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } } } _sprites.Clear(); if ((Object)(object)PingClip != (Object)null) { Object.Destroy((Object)(object)PingClip); } if (_ownsFont && (Object)(object)Font != (Object)null) { Object.Destroy((Object)(object)Font); } } private Sprite Add(Sprite sprite) { _sprites.Add(sprite); return sprite; } private static Color Parse(string value, Color fallback) { //IL_000c: 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) Color result = default(Color); if (!ColorUtility.TryParseHtmlString(value, ref result)) { return fallback; } return result; } private static AudioClip CreatePingClip() { int num = Mathf.CeilToInt(7056f); float[] array = new float[num]; for (int i = 0; i < num; i++) { float num2 = (float)i / 44100f; float num3 = ((num2 < 0.055f) ? 690f : 980f); float num4 = Mathf.Exp(-18f * num2) * Mathf.Clamp01(num2 * 90f); array[i] = Mathf.Sin(MathF.PI * 2f * num3 * num2) * num4 * 0.36f; } AudioClip obj = AudioClip.Create("TeamPing_Sound", num, 1, 44100, false); ((Object)obj).hideFlags = (HideFlags)61; obj.SetData(array, 0); return obj; } } } namespace TeamPing.Network { public sealed class PingNetwork : IDisposable { private const byte EventCode = 181; private const string Signature = "com.juanma.teamping"; private const byte ProtocolVersion = 1; private const float RemoteMinimumInterval = 0.15f; private readonly ModConfig _config; private readonly ManualLogSource _logger; private readonly Dictionary _lastRemoteEventTime = new Dictionary(); private LoadBalancingClient? _subscribedClient; public event Action? PingReceived; public PingNetwork(ModConfig config, ManualLogSource logger) { _config = config; _logger = logger; RefreshSubscription(); } public void Tick() { RefreshSubscription(); } public bool Broadcast(PingData ping) { //IL_0055: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.InRoom || !PhotonNetwork.IsConnectedAndReady) { return false; } object[] array = new object[10] { "com.juanma.teamping", (byte)1, (byte)ping.Kind, ping.PingId, ping.ViewId, ping.WorldPosition, ping.SurfaceNormal, SanitizeLabel(ping.Label), ping.HasValue ? ping.Value : (-1), ping.ServerTimestamp }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0, CachingOption = (EventCaching)0 }; bool flag = PhotonNetwork.RaiseEvent((byte)181, (object)array, val, SendOptions.SendReliable); if (_config.DebugLogging.Value) { _logger.LogInfo((object)string.Format("[TeamPing/Network] Outgoing ping: sent={0}, actor={1}, id={2}, kind={3}, view={4}, label='{5}', value={6}", flag, ping.SenderActorNumber, ping.PingId, ping.Kind, ping.ViewId, ping.Label, ping.HasValue ? ping.Value.ToString() : "hidden")); } return flag; } public string GetActorName(int actorNumber) { if (actorNumber <= 0) { return "PLAYER"; } try { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && currentRoom.Players.TryGetValue(actorNumber, out var value) && !string.IsNullOrWhiteSpace(value.NickName)) { return value.NickName.Trim(); } } catch { } return "PLAYER"; } private void RefreshSubscription() { LoadBalancingClient networkingClient = PhotonNetwork.NetworkingClient; if (networkingClient != _subscribedClient) { if (_subscribedClient != null) { _subscribedClient.EventReceived -= OnEventReceived; } _subscribedClient = networkingClient; if (_subscribedClient != null) { _subscribedClient.EventReceived += OnEventReceived; _logger.LogInfo((object)"[TeamPing/Network] Photon custom-event listener attached."); } } } private void OnEventReceived(EventData eventData) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: 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) if (eventData.Code != 181 || !(eventData.CustomData is object[] array) || array.Length != 10 || !(array[0] is string text) || text != "com.juanma.teamping" || !(array[1] is byte b) || b != 1) { return; } int sender = eventData.Sender; if (sender <= 0 || !AllowRemoteSender(sender)) { return; } if (!(array[2] is byte b2) || !Enum.IsDefined(typeof(PingTargetKind), b2) || !(array[3] is int num) || !(array[4] is int num2) || !(array[5] is Vector3 val) || !(array[6] is Vector3 val2) || !(array[7] is string value) || !(array[8] is int num3) || !(array[9] is int num4)) { if (_config.DebugLogging.Value) { _logger.LogWarning((object)$"[TeamPing/Network] Rejected malformed payload from actor {sender}."); } } else { if (!IsFinite(val) || !IsFinite(val2) || ((Vector3)(ref val)).sqrMagnitude > 1E+09f) { return; } int num5 = PhotonNetwork.ServerTimestamp - num4; if (num5 <= 15000 && num5 >= -2000) { string label = SanitizeLabel(value); int num6 = Mathf.Clamp(num3, -1, 100000000); PingData pingData = new PingData(num, sender, GetActorName(sender), (PingTargetKind)b2, Mathf.Max(0, num2), val, val2, label, num6 >= 0, Mathf.Max(0, num6), num4); if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[TeamPing/Network] Incoming ping: actor={sender}, id={num}, kind={pingData.Kind}, view={pingData.ViewId}, ageMs={num5}"); } this.PingReceived?.Invoke(pingData); } } } private bool AllowRemoteSender(int actorNumber) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_lastRemoteEventTime.TryGetValue(actorNumber, out var value) && realtimeSinceStartup - value < 0.15f) { return false; } _lastRemoteEventTime[actorNumber] = realtimeSinceStartup; return true; } public void Dispose() { if (_subscribedClient != null) { _subscribedClient.EventReceived -= OnEventReceived; } _subscribedClient = null; _lastRemoteEventTime.Clear(); this.PingReceived = null; } private static string SanitizeLabel(string? value) { if (string.IsNullOrWhiteSpace(value)) { return "PING"; } char[] array = new char[Mathf.Min(64, value.Length)]; int num = 0; string text = value.Trim(); foreach (char c in text) { if (num >= array.Length) { break; } if (!char.IsControl(c)) { array[num++] = c; } } if (num != 0) { return new string(array, 0, num); } return "PING"; } private static bool IsFinite(Vector3 value) { //IL_0000: 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_001a: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(value.x) && IsFinite(value.y)) { return IsFinite(value.z); } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } } namespace TeamPing.Core { public sealed class GameBindings { private readonly ManualLogSource _logger; private FieldInfo? _valuableCurrentValueField; private FieldInfo? _valuableValueSetField; private FieldInfo? _itemValueField; private FieldInfo? _itemNameField; private FieldInfo? _hasNeverBeenGrabbedField; private FieldInfo? _deadSetField; private FieldInfo? _isDisabledField; private FieldInfo? _runStartedField; private bool _readFailureReported; public GameBindings(ManualLogSource logger) { _logger = logger; } public bool Resolve() { _valuableCurrentValueField = ResolveField(typeof(ValuableObject), "dollarValueCurrent", typeof(float)); _valuableValueSetField = ResolveField(typeof(ValuableObject), "dollarValueSet", typeof(bool)); _itemValueField = ResolveField(typeof(ItemAttributes), "value", typeof(int)); _itemNameField = ResolveField(typeof(ItemAttributes), "itemName", typeof(string)); _hasNeverBeenGrabbedField = ResolveField(typeof(PhysGrabObject), "hasNeverBeenGrabbed", typeof(bool)); _deadSetField = ResolveField(typeof(PlayerAvatar), "deadSet", typeof(bool)); _isDisabledField = ResolveField(typeof(PlayerAvatar), "isDisabled", typeof(bool)); _runStartedField = ResolveField(typeof(RunManager), "runStarted", typeof(bool)); if (_valuableCurrentValueField != null && _itemValueField != null && _deadSetField != null) { return _runStartedField != null; } return false; } public bool IsGameplayActive(out string phase) { phase = "unavailable"; try { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { phase = "RunManager missing"; return false; } if (SemiFunc.IsMainMenu()) { phase = "main menu"; return false; } if (!TryRead(_runStartedField, instance, out var value) || !value) { phase = "run not started"; return false; } if (SemiFunc.RunIsLevel()) { phase = "mission level"; } else if (SemiFunc.RunIsShop()) { phase = "shop"; } else if (SemiFunc.RunIsLobby()) { phase = "run lobby"; } else if (SemiFunc.RunIsTutorial()) { phase = "tutorial"; } else if (SemiFunc.RunIsArena()) { phase = "arena"; } else { phase = "active run"; } return true; } catch (Exception ex) { phase = "state error: " + ex.GetType().Name; return false; } } public PlayerAvatar? GetLocalPlayer() { try { PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); if ((Object)(object)val != (Object)null) { return val; } } catch { } try { return ((Object)(object)PlayerController.instance != (Object)null) ? PlayerController.instance.playerAvatarScript : null; } catch { return null; } } public Camera? GetGameplayCamera() { try { if ((Object)(object)CameraUtils.Instance != (Object)null && (Object)(object)CameraUtils.Instance.MainCamera != (Object)null) { return CameraUtils.Instance.MainCamera; } } catch { } return Camera.main; } public bool CanLocalPlayerPing(out string reason) { reason = string.Empty; PlayerAvatar localPlayer = GetLocalPlayer(); if ((Object)(object)localPlayer == (Object)null) { reason = "local PlayerAvatar unavailable"; return false; } if (TryRead(_deadSetField, localPlayer, out var value) && value) { reason = "local player is dead"; return false; } if (TryRead(_isDisabledField, localPlayer, out var value2) && value2) { reason = "local player is disabled"; return false; } if ((Object)(object)GetGameplayCamera() == (Object)null) { reason = "gameplay camera unavailable"; return false; } try { if ((Object)(object)PlayerController.instance != (Object)null && !PlayerController.instance.CanInteract) { reason = "gameplay interaction is currently disabled"; return false; } } catch { } return true; } public string GetPlayerName(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return "PLAYER"; } try { string text = SemiFunc.PlayerGetName(player); return string.IsNullOrWhiteSpace(text) ? "PLAYER" : text.Trim(); } catch { return "PLAYER"; } } public string GetItemName(ItemAttributes item) { if (TryRead(_itemNameField, item, out var value) && !string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return CleanObjectName(((Object)((Component)item).gameObject).name, "ITEM"); } public bool TryGetValuableValue(ValuableObject valuable, PhysGrabObject? physObject, ValueDisplayMode mode, out int value) { value = 0; switch (mode) { case ValueDisplayMode.Never: return false; case ValueDisplayMode.AfterFirstGrab: if (HasNeverBeenGrabbed(physObject)) { return false; } break; } if (_valuableValueSetField != null && TryRead(_valuableValueSetField, valuable, out var value2) && !value2) { return false; } if (!TryRead(_valuableCurrentValueField, valuable, out var value3) || !IsFinite(value3) || value3 < 0f) { return false; } value = Mathf.RoundToInt(value3); return true; } public bool TryGetItemValue(ItemAttributes item, PhysGrabObject? physObject, ValueDisplayMode mode, out int value) { value = 0; switch (mode) { case ValueDisplayMode.Never: return false; case ValueDisplayMode.AfterFirstGrab: if (HasNeverBeenGrabbed(physObject)) { return false; } break; } if (TryRead(_itemValueField, item, out value)) { return value > 0; } return false; } private bool HasNeverBeenGrabbed(PhysGrabObject? physObject) { bool value = default(bool); return (Object)(object)physObject != (Object)null && TryRead(_hasNeverBeenGrabbedField, physObject, out value) && value; } public static string CleanObjectName(string? rawName, string fallback) { if (string.IsNullOrWhiteSpace(rawName)) { return fallback; } string text = rawName.Replace("(Clone)", string.Empty).Trim(); string[] array = new string[6] { "Valuable ", "Valuable_", "Valuable-", "Item ", "Item_", "Prefab " }; foreach (string text2 in array) { if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { text = text.Substring(text2.Length); break; } } text = text.Replace('_', ' ').Replace('-', ' '); StringBuilder stringBuilder = new StringBuilder(text.Length + 8); for (int j = 0; j < text.Length; j++) { char c = text[j]; if (j > 0 && char.IsUpper(c) && char.IsLower(text[j - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } string text3 = stringBuilder.ToString().Trim(); if (!string.IsNullOrWhiteSpace(text3)) { return text3; } return fallback; } private FieldInfo? ResolveField(Type type, string name, Type expectedType) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null || field.FieldType != expectedType) { _logger.LogWarning((object)("[TeamPing/Bindings] Missing field " + type.Name + "." + name + " (" + expectedType.Name + ").")); return null; } return field; } private bool TryRead(FieldInfo? field, object target, out T value) { value = default(T); if (field == null || target == null) { return false; } try { if (field.GetValue(target) is T val) { value = val; return true; } } catch (Exception ex) { if (!_readFailureReported) { _readFailureReported = true; _logger.LogWarning((object)("[TeamPing/Bindings] Field read failed: " + ex.GetType().Name + ": " + ex.Message)); } } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public sealed class PingController : IDisposable { private readonly ModConfig _config; private readonly GameBindings _bindings; private readonly PingTargetResolver _targetResolver; private readonly PingNetwork _network; private readonly PingHudController _hud; private readonly ManualLogSource _logger; private int _lastTickFrame = -1; private int _nextPingId; private float _lastLocalPingTime = float.NegativeInfinity; private string _lastPhase = string.Empty; private bool _disposed; public PingController(ModConfig config, GameBindings bindings, ManualLogSource logger) { _config = config; _bindings = bindings; _logger = logger; _targetResolver = new PingTargetResolver(bindings, config, logger); _network = new PingNetwork(config, logger); _hud = new PingHudController(config, logger); _network.PingReceived += OnRemotePing; _config.Changed += OnConfigChanged; } public void Tick(float unscaledDeltaTime) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) if (_disposed || _lastTickFrame == Time.frameCount) { return; } _lastTickFrame = Time.frameCount; _config.PollForExternalChanges(unscaledDeltaTime); _network.Tick(); string phase = (_config.Enabled.Value ? "checking" : "disabled by config"); bool flag = _config.Enabled.Value && _bindings.IsGameplayActive(out phase); if (_config.DebugLogging.Value && _lastPhase != phase) { _lastPhase = phase; _logger.LogInfo((object)$"[TeamPing/Lifecycle] Gameplay phase changed: {phase}, active={flag}"); } _hud.Tick(unscaledDeltaTime, _bindings.GetGameplayCamera(), flag); if (flag) { KeyboardShortcut value = _config.PingKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { TryCreateLocalPing(); } } } public void HandleSceneChanged() { _hud.Clear(); _lastLocalPingTime = float.NegativeInfinity; if (_config.DebugLogging.Value) { _logger.LogInfo((object)"[TeamPing/Lifecycle] Scene changed; active pings cleared."); } } private void TryCreateLocalPing() { //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) float unscaledTime = Time.unscaledTime; float num = Mathf.Clamp(_config.Cooldown.Value, 0.1f, 10f); if (unscaledTime - _lastLocalPingTime < num) { return; } if (!_bindings.CanLocalPlayerPing(out string reason)) { if (_config.DebugLogging.Value) { _logger.LogInfo((object)("[TeamPing/Input] Ping ignored: " + reason + ".")); } return; } if (!_targetResolver.TryResolve(out PingTarget target, out reason)) { if (_config.DebugLogging.Value) { _logger.LogInfo((object)("[TeamPing/Input] No ping created: " + reason + ".")); } return; } _lastLocalPingTime = unscaledTime; int num2 = ((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0); string senderName = ((num2 > 0) ? _network.GetActorName(num2) : _bindings.GetPlayerName(_bindings.GetLocalPlayer())); int serverTimestamp = (PhotonNetwork.IsConnected ? PhotonNetwork.ServerTimestamp : Environment.TickCount); PingData ping = new PingData(++_nextPingId, num2, senderName, target.Kind, target.ViewId, target.WorldPosition, target.SurfaceNormal, target.Label, target.HasValue, target.Value, serverTimestamp); _hud.Show(ping); _network.Broadcast(ping); if (_config.DebugLogging.Value) { _logger.LogInfo((object)string.Format("[TeamPing/Input] Created {0} ping at {1}, view={2}, label='{3}', value={4}", target.Kind, target.WorldPosition, target.ViewId, target.Label, target.HasValue ? target.Value.ToString() : "hidden")); } } private void OnRemotePing(PingData ping) { if (_config.Enabled.Value && _bindings.IsGameplayActive(out string _)) { _hud.Show(ping); } } private void OnConfigChanged() { _hud.ApplyConfig(); _logger.LogInfo((object)"[TeamPing/Config] Configuration applied without restarting the game."); } public void Dispose() { if (!_disposed) { _disposed = true; _config.Changed -= OnConfigChanged; _network.PingReceived -= OnRemotePing; _network.Dispose(); _hud.Dispose(); } } } public enum PingTargetKind : byte { Location, Valuable, Item, Cart, Player, Object } public readonly struct PingTarget { public PingTargetKind Kind { get; } public int ViewId { get; } public Vector3 WorldPosition { get; } public Vector3 SurfaceNormal { get; } public string Label { get; } public bool HasValue { get; } public int Value { get; } public PingTarget(PingTargetKind kind, int viewId, Vector3 worldPosition, Vector3 surfaceNormal, string label, bool hasValue, int value) { //IL_000f: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) Kind = kind; ViewId = viewId; WorldPosition = worldPosition; SurfaceNormal = surfaceNormal; Label = label; HasValue = hasValue; Value = value; } } public sealed class PingData { public int PingId { get; } public int SenderActorNumber { get; } public string SenderName { get; } public PingTargetKind Kind { get; } public int ViewId { get; } public Vector3 WorldPosition { get; } public Vector3 SurfaceNormal { get; } public string Label { get; } public bool HasValue { get; } public int Value { get; } public int ServerTimestamp { get; } public PingData(int pingId, int senderActorNumber, string senderName, PingTargetKind kind, int viewId, Vector3 worldPosition, Vector3 surfaceNormal, string label, bool hasValue, int value, int serverTimestamp) { //IL_002c: 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_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) PingId = pingId; SenderActorNumber = senderActorNumber; SenderName = senderName; Kind = kind; ViewId = viewId; WorldPosition = worldPosition; SurfaceNormal = surfaceNormal; Label = label; HasValue = hasValue; Value = value; ServerTimestamp = serverTimestamp; } } public sealed class PingTargetResolver { private readonly GameBindings _bindings; private readonly ModConfig _config; private readonly ManualLogSource _logger; public PingTargetResolver(GameBindings bindings, ModConfig config, ManualLogSource logger) { _bindings = bindings; _config = config; _logger = logger; } public bool TryResolve(out PingTarget target, out string failure) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) target = default(PingTarget); failure = string.Empty; Camera gameplayCamera = _bindings.GetGameplayCamera(); PlayerAvatar localPlayer = _bindings.GetLocalPlayer(); if ((Object)(object)gameplayCamera == (Object)null) { failure = "gameplay camera unavailable"; return false; } float num = Mathf.Clamp(_config.Range.Value, 2f, 100f); RaycastHit[] array = Physics.RaycastAll(gameplayCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f)), num, -5, (QueryTriggerInteraction)1); if (array.Length == 0) { failure = "the crosshair did not hit a surface"; return false; } Array.Sort(array, (RaycastHit left, RaycastHit right) => ((RaycastHit)(ref left)).distance.CompareTo(((RaycastHit)(ref right)).distance)); RaycastHit[] array2 = array; for (int num2 = 0; num2 < array2.Length; num2++) { RaycastHit hit = array2[num2]; Collider collider = ((RaycastHit)(ref hit)).collider; if ((Object)(object)collider == (Object)null || ((Object)(object)localPlayer != (Object)null && ((Component)collider).transform.IsChildOf(((Component)localPlayer).transform))) { continue; } try { target = Classify(hit, localPlayer); return true; } catch (MissingReferenceException) { } catch (Exception ex2) { failure = ex2.GetType().Name + ": " + ex2.Message; if (_config.DebugLogging.Value) { _logger.LogWarning((object)("[TeamPing/Target] Failed to classify hit '" + ((Object)collider).name + "': " + failure)); } return false; } } failure = "only the local player was hit"; return false; } private PingTarget Classify(RaycastHit hit, PlayerAvatar? localPlayer) { //IL_006d: 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_002f: 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_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0191: 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_0169: Unknown result type (might be due to invalid IL or missing references) Collider collider = ((RaycastHit)(ref hit)).collider; PlayerAvatar componentInParent = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent != (Object)(object)localPlayer) { return new PingTarget(PingTargetKind.Player, ViewId(componentInParent.photonView), ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, _bindings.GetPlayerName(componentInParent), hasValue: false, 0); } PhysGrabCart componentInParent2 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { return new PingTarget(PingTargetKind.Cart, ViewId(((Component)componentInParent2).GetComponentInParent()), ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, "C.A.R.T.", hasValue: false, 0); } PhysGrabObject componentInParent3 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null) { int viewId = ViewId(((Component)componentInParent3).GetComponent() ?? ((Component)componentInParent3).GetComponentInParent()); ValuableObject val = ((Component)componentInParent3).GetComponent() ?? ((Component)componentInParent3).GetComponentInChildren(); if ((Object)(object)val != (Object)null) { int value; bool hasValue = _bindings.TryGetValuableValue(val, componentInParent3, _config.ValueMode.Value, out value); return new PingTarget(PingTargetKind.Valuable, viewId, ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, GameBindings.CleanObjectName(((Object)((Component)val).gameObject).name, "VALUABLE"), hasValue, value); } ItemAttributes val2 = ((Component)componentInParent3).GetComponent() ?? ((Component)componentInParent3).GetComponentInChildren(); if ((Object)(object)val2 != (Object)null) { int value2; bool hasValue2 = _bindings.TryGetItemValue(val2, componentInParent3, _config.ValueMode.Value, out value2); return new PingTarget(PingTargetKind.Item, viewId, ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, _bindings.GetItemName(val2), hasValue2, value2); } return new PingTarget(PingTargetKind.Object, viewId, ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, GameBindings.CleanObjectName(((Object)((Component)componentInParent3).gameObject).name, "OBJECT"), hasValue: false, 0); } string label = (string.IsNullOrWhiteSpace(_config.LocationLabel.Value) ? "LOOK HERE" : _config.LocationLabel.Value.Trim()); return new PingTarget(PingTargetKind.Location, 0, ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, label, hasValue: false, 0); } private static int ViewId(PhotonView? view) { if (!((Object)(object)view != (Object)null) || view.ViewID <= 0) { return 0; } return view.ViewID; } } } namespace TeamPing.Config { public enum ValueDisplayMode { Always, AfterFirstGrab, Never } public sealed class ModConfig : IDisposable { private const float ExternalReloadInterval = 0.5f; private readonly List _unsubscribeActions = new List(); private readonly ConfigFile _configFile; private DateTime _lastWriteTimeUtc; private long _lastFileLength; private float _externalReloadTimer; private bool _isReloading; public ConfigEntry Enabled { get; } public ConfigEntry PingKey { get; } public ConfigEntry Range { get; } public ConfigEntry Cooldown { get; } public ConfigEntry Duration { get; } public ConfigEntry MaxPingsPerPlayer { get; } public ConfigEntry ValueMode { get; } public ConfigEntry LocationLabel { get; } public ConfigEntry ShowSender { get; } public ConfigEntry ShowDistance { get; } public ConfigEntry ScreenEdgeIndicators { get; } public ConfigEntry MarkerScale { get; } public ConfigEntry SoundEnabled { get; } public ConfigEntry SoundVolume { get; } public ConfigEntry ValuableColor { get; } public ConfigEntry ItemColor { get; } public ConfigEntry CartColor { get; } public ConfigEntry PlayerColor { get; } public ConfigEntry LocationColor { get; } public ConfigEntry DebugLogging { get; } public event Action? Changed; public ModConfig(ConfigFile config) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) _configFile = config; Enabled = Bind(config, "General", "Enabled", defaultValue: true, "Enable TeamPing."); PingKey = Bind(config, "General", "PingKey", new KeyboardShortcut((KeyCode)325, Array.Empty()), "Press to ping the target under the crosshair."); Range = Bind(config, "General", "Range", 20f, "Maximum ping distance in metres."); Cooldown = Bind(config, "General", "Cooldown", 0.6f, "Minimum seconds between local pings."); Duration = Bind(config, "General", "Duration", 6f, "Seconds before a ping fades out."); MaxPingsPerPlayer = Bind(config, "General", "MaxPingsPerPlayer", 3, "Maximum simultaneous pings kept for each player."); ValueMode = Bind(config, "General", "ValueMode", ValueDisplayMode.Always, "Always, AfterFirstGrab, or Never. AfterFirstGrab is the most vanilla-faithful option."); LocationLabel = Bind(config, "General", "LocationLabel", "LOOK HERE", "Label used for surface/location pings."); ShowSender = Bind(config, "Appearance", "ShowSender", defaultValue: true, "Show who created each ping."); ShowDistance = Bind(config, "Appearance", "ShowDistance", defaultValue: true, "Show current distance to each ping."); ScreenEdgeIndicators = Bind(config, "Appearance", "ScreenEdgeIndicators", defaultValue: true, "Clamp off-screen pings to the screen edge."); MarkerScale = Bind(config, "Appearance", "MarkerScale", 1f, "Marker scale multiplier."); SoundEnabled = Bind(config, "Appearance", "SoundEnabled", defaultValue: true, "Play a short sound when a ping appears."); SoundVolume = Bind(config, "Appearance", "SoundVolume", 0.65f, "Ping sound volume from 0 to 1."); ValuableColor = Bind(config, "Appearance", "ValuableColor", "#61FF86", "Valuable marker color in HTML hex format."); ItemColor = Bind(config, "Appearance", "ItemColor", "#55D8FF", "Item marker color in HTML hex format."); CartColor = Bind(config, "Appearance", "CartColor", "#FFAE42", "C.A.R.T. marker color in HTML hex format."); PlayerColor = Bind(config, "Appearance", "PlayerColor", "#D58AFF", "Player marker color in HTML hex format."); LocationColor = Bind(config, "Appearance", "LocationColor", "#FFF1A8", "Location marker color in HTML hex format."); DebugLogging = Bind(config, "Debug", "DebugLogging", defaultValue: false, "Write useful ping diagnostics without per-frame spam."); CaptureFileState(); } public bool PollForExternalChanges(float unscaledDeltaTime) { _externalReloadTimer -= Mathf.Max(0f, unscaledDeltaTime); if (_externalReloadTimer > 0f) { return false; } _externalReloadTimer = 0.5f; try { string configFilePath = _configFile.ConfigFilePath; if (!File.Exists(configFilePath)) { return false; } FileInfo fileInfo = new FileInfo(configFilePath); if (fileInfo.LastWriteTimeUtc == _lastWriteTimeUtc && fileInfo.Length == _lastFileLength) { return false; } bool saveOnConfigSet = _configFile.SaveOnConfigSet; try { _isReloading = true; _configFile.SaveOnConfigSet = false; _configFile.Reload(); } finally { _isReloading = false; _configFile.SaveOnConfigSet = saveOnConfigSet; } CaptureFileState(); this.Changed?.Invoke(); return true; } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } } private ConfigEntry Bind(ConfigFile config, string section, string key, T defaultValue, string description) { ConfigEntry entry = config.Bind(section, key, defaultValue, description); entry.SettingChanged += OnSettingChanged; _unsubscribeActions.Add(delegate { entry.SettingChanged -= OnSettingChanged; }); return entry; } private void OnSettingChanged(object sender, EventArgs args) { if (!_isReloading) { this.Changed?.Invoke(); } } private void CaptureFileState() { string configFilePath = _configFile.ConfigFilePath; if (File.Exists(configFilePath)) { FileInfo fileInfo = new FileInfo(configFilePath); _lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; _lastFileLength = fileInfo.Length; } } public void Dispose() { foreach (Action unsubscribeAction in _unsubscribeActions) { unsubscribeAction(); } _unsubscribeActions.Clear(); } } }