Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TeamPing v1.0.1
plugins\TeamPing.dll
Decompiled 11 hours agousing 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<PingMarker> _markers = new List<PingMarker>(); 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<Canvas>(); component.renderMode = (RenderMode)0; component.sortingOrder = 32000; CanvasScaler component2 = _canvasRoot.GetComponent<CanvasScaler>(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; _audioSource = _canvasRoot.GetComponent<AudioSource>(); _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>(); _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<Image>(); _diamond.sprite = styles.DiamondBorderSprite; ((Graphic)_diamond).raycastTarget = false; _diamondGlow = val.GetComponent<Shadow>(); _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<Image>(); 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<Image>(); _dot.sprite = styles.DotSprite; ((Graphic)_dot).raycastTarget = false; Shadow component2 = val4.GetComponent<Shadow>(); 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<Image>(); _panel.sprite = styles.PanelSprite; ((Graphic)_panel).raycastTarget = false; Shadow component3 = val5.GetComponent<Shadow>(); 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<Image>(); ((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<Text>(); ConfigureText(_labelText, 16, (FontStyle)1, (TextAnchor)3); _labelText.resizeTextForBestFit = true; _labelText.resizeTextMinSize = 10; _labelText.resizeTextMaxSize = 16; Shadow component4 = val8.GetComponent<Shadow>(); 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<Text>(); 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<Text>(); ConfigureText(_edgeArrow, 21, (FontStyle)1, (TextAnchor)4); _edgeArrow.text = "▲"; Shadow component5 = val10.GetComponent<Shadow>(); 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<string> list = new List<string>(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<Sprite> _sprites = new List<Sprite>(); 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<Font>("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<int, float> _lastRemoteEventTime = new Dictionary<int, float>(); private LoadBalancingClient? _subscribedClient; public event Action<PingData>? 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<bool>(_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<bool>(_deadSetField, localPlayer, out var value) && value) { reason = "local player is dead"; return false; } if (TryRead<bool>(_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<string>(_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<bool>(_valuableValueSetField, valuable, out var value2) && !value2) { return false; } if (!TryRead<float>(_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<int>(_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<bool>(_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<T>(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<PlayerAvatar>(); 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<PhysGrabCart>(); if ((Object)(object)componentInParent2 != (Object)null) { return new PingTarget(PingTargetKind.Cart, ViewId(((Component)componentInParent2).GetComponentInParent<PhotonView>()), ((RaycastHit)(ref hit)).point, ((RaycastHit)(ref hit)).normal, "C.A.R.T.", hasValue: false, 0); } PhysGrabObject componentInParent3 = ((Component)collider).GetComponentInParent<PhysGrabObject>(); if ((Object)(object)componentInParent3 != (Object)null) { int viewId = ViewId(((Component)componentInParent3).GetComponent<PhotonView>() ?? ((Component)componentInParent3).GetComponentInParent<PhotonView>()); ValuableObject val = ((Component)componentInParent3).GetComponent<ValuableObject>() ?? ((Component)componentInParent3).GetComponentInChildren<ValuableObject>(); 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<ItemAttributes>() ?? ((Component)componentInParent3).GetComponentInChildren<ItemAttributes>(); 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<Action> _unsubscribeActions = new List<Action>(); private readonly ConfigFile _configFile; private DateTime _lastWriteTimeUtc; private long _lastFileLength; private float _externalReloadTimer; private bool _isReloading; public ConfigEntry<bool> Enabled { get; } public ConfigEntry<KeyboardShortcut> PingKey { get; } public ConfigEntry<float> Range { get; } public ConfigEntry<float> Cooldown { get; } public ConfigEntry<float> Duration { get; } public ConfigEntry<int> MaxPingsPerPlayer { get; } public ConfigEntry<ValueDisplayMode> ValueMode { get; } public ConfigEntry<string> LocationLabel { get; } public ConfigEntry<bool> ShowSender { get; } public ConfigEntry<bool> ShowDistance { get; } public ConfigEntry<bool> ScreenEdgeIndicators { get; } public ConfigEntry<float> MarkerScale { get; } public ConfigEntry<bool> SoundEnabled { get; } public ConfigEntry<float> SoundVolume { get; } public ConfigEntry<string> ValuableColor { get; } public ConfigEntry<string> ItemColor { get; } public ConfigEntry<string> CartColor { get; } public ConfigEntry<string> PlayerColor { get; } public ConfigEntry<string> LocationColor { get; } public ConfigEntry<bool> 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<KeyboardShortcut>(config, "General", "PingKey", new KeyboardShortcut((KeyCode)325, Array.Empty<KeyCode>()), "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<T> Bind<T>(ConfigFile config, string section, string key, T defaultValue, string description) { ConfigEntry<T> entry = config.Bind<T>(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(); } } }