Decompiled source of PeakTwitchIntegration v0.8.6
plugins/PeakTwitchIntegration.dll
Decompiled 2 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Peak.UI; using Photon.Pun; using Photon.Realtime; using Photon.Voice; using Photon.Voice.Unity; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; 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("PeakTwitchIntegration")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.8.6.0")] [assembly: AssemblyInformationalVersion("0.8.6")] [assembly: AssemblyProduct("PeakTwitchIntegration")] [assembly: AssemblyTitle("PeakTwitchIntegration")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.8.6.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PeakTwitchIntegration { internal static class CannonLaunchController { private const byte FallProtectionEventCode = 173; private const byte CountdownEventCode = 174; private const byte TeammatePushFallProtectionEventCode = 175; private const string NetworkMarker = "com.local.peaktwitchintegration:cannon-launch:v1"; private const string CountdownNetworkMarker = "com.local.peaktwitchintegration:cannon-countdown:v1"; private const string TeammatePushProtectionNetworkMarker = "com.local.peaktwitchintegration:teammate-push-protection:v1"; private const float FallProtectionSeconds = 300f; private const int CountdownSeconds = 3; private const float ForwardForceRatio = 0.15f; private static bool _initialized; private static CannonLaunchRuntime? _runtime; internal static void Initialize(MonoBehaviour host) { if (!_initialized) { _runtime = ((Component)host).GetComponent<CannonLaunchRuntime>() ?? ((Component)host).gameObject.AddComponent<CannonLaunchRuntime>(); PhotonNetwork.NetworkingClient.EventReceived += OnPhotonEvent; _initialized = true; } } internal static void Shutdown() { if (_initialized) { PhotonNetwork.NetworkingClient.EventReceived -= OnPhotonEvent; FallDamageSafety.Clear(); _runtime?.ClearCountdowns(); _runtime = null; _initialized = false; } } internal static void HandleRedemption(CannonLaunchRedemption redemption) { if (!Plugin.CannonLaunchEnabled) { Reject(redemption, "Cannon Launch is disabled; Channel Points were refunded."); return; } try { if (!TryFindPlayer(redemption.PlayerName, out Character target, out string problem)) { Reject(redemption, problem + " Channel Points were refunded."); return; } if ((Object)(object)target == (Object)null || (Object)(object)target.data == (Object)null || target.data.dead || target.data.fullyPassedOut) { Reject(redemption, "'" + redemption.PlayerName + "' cannot be launched right now; Channel Points were refunded."); return; } int ownerActorNr = ((MonoBehaviourPun)target).photonView.OwnerActorNr; string bestName = GetBestName(target); if ((Object)(object)_runtime == (Object)null) { throw new InvalidOperationException("Cannon Launch runtime is unavailable."); } ShowLocalCountdown(ownerActorNr, 3, "[PEAK] Cannon Launch"); BroadcastCountdown(ownerActorNr, 3, "[PEAK] Cannon Launch"); string text = $"{redemption.Viewer} selected {bestName}; Cannon Launch in {3} seconds."; Plugin.Log.LogInfo((object)text); Plugin.Twitch.ReportCannonLaunchResult(text); ((MonoBehaviour)_runtime).StartCoroutine(LaunchAfterCountdown(redemption, ownerActorNr, bestName)); } catch (Exception arg) { Plugin.Log.LogError((object)$"Cannon Launch failed: {arg}"); Reject(redemption, "Cannon Launch failed; Channel Points were refunded."); } } internal static void ProtectFromTeammatePush(Character target, float seconds) { //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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null) && !((Object)(object)((MonoBehaviourPun)target).photonView == (Object)null)) { int ownerActorNr = ((MonoBehaviourPun)target).photonView.OwnerActorNr; FallDamageSafety.Begin(ownerActorNr, seconds, "Teammate Push"); if (PhotonNetwork.InRoom) { object[] array = new object[3] { "com.local.peaktwitchintegration:teammate-push-protection:v1", ownerActorNr, Mathf.Clamp(seconds, 1f, 30f) }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)175, (object)array, val, SendOptions.SendReliable); } } } private static IEnumerator LaunchAfterCountdown(CannonLaunchRedemption redemption, int actorNumber, string resolvedName) { yield return (object)new WaitForSecondsRealtime(3f); CompleteLaunch(redemption, actorNumber, resolvedName); } private static void CompleteLaunch(CannonLaunchRedemption redemption, int actorNumber, string previousName) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CannonLaunchEnabled) { Reject(redemption, "Cannon Launch was disabled during the countdown; Channel Points were refunded."); return; } try { if (!TryFindPlayer(actorNumber, out Character target) || (Object)(object)target == (Object)null) { Reject(redemption, "'" + previousName + "' left before Cannon Launch; Channel Points were refunded."); return; } if ((Object)(object)target.data == (Object)null || target.data.dead || target.data.fullyPassedOut) { Reject(redemption, "'" + previousName + "' cannot be launched now; Channel Points were refunded."); return; } BeginLocalProtection(actorNumber); BroadcastFallProtection(actorNumber); Vector3 val = BuildLaunchForce(target); if (PhotonNetwork.InRoom && (Object)(object)target.refs.view != (Object)null) { target.refs.view.RPC("RPCA_Fall", (RpcTarget)0, new object[2] { 1.25f, 0f }); target.refs.view.RPC("RPCA_AddForceToBodyPart", (RpcTarget)0, new object[3] { (object)(BodypartType)2, Vector3.zero, val }); } else { target.RPCA_Fall(1.25f, 0f); target.RPCA_AddForceToBodyPart((BodypartType)2, Vector3.zero, val); } string bestName = GetBestName(target); string text = redemption.Viewer + " launched " + bestName + " with Cannon Launch."; Plugin.Log.LogInfo((object)text); Plugin.Twitch.ReportCannonLaunchResult(text); Plugin.Twitch.CompleteCannonLaunch(redemption, fulfilled: true); } catch (Exception arg) { Plugin.Log.LogError((object)$"Cannon Launch failed after countdown: {arg}"); Reject(redemption, "Cannon Launch failed; Channel Points were refunded."); } } private static Vector3 BuildLaunchForce(Character target) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)target).transform.forward; if (target.refs != null && (Object)(object)target.refs.view != (Object)null) { val = ((Component)target.refs.view).transform.forward; } val = Vector3.ProjectOnPlane(val, Vector3.up); if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); return (Vector3.up + val * 0.15f) * Plugin.CannonLaunchForce; } private static void Reject(CannonLaunchRedemption redemption, string message) { Plugin.Log.LogWarning((object)message); Plugin.Twitch.ReportCannonLaunchResult(message); Plugin.Twitch.CompleteCannonLaunch(redemption, fulfilled: false); } private static bool TryFindPlayer(string requestedName, out Character? target, out string problem) { target = null; string text = NormalizeName(requestedName); if (text.Length == 0) { problem = "No PEAK player name was entered."; return false; } List<Character> list = new List<Character>(); foreach (Character allCharacter in Character.AllCharacters) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.isBot && !allCharacter.isZombie && !allCharacter.isScoutmaster) { string text2 = NormalizeName(allCharacter.characterName); PhotonView photonView = ((MonoBehaviourPun)allCharacter).photonView; object obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? owner.NickName : null); } if (obj == null) { obj = string.Empty; } string text3 = NormalizeName((string)obj); if (text2 == text || text3 == text) { list.Add(allCharacter); } } } if (list.Count == 1) { target = list[0]; problem = string.Empty; return true; } if (list.Count > 1) { problem = "The name '" + requestedName + "' is ambiguous in this lobby."; return false; } problem = "No active PEAK player named '" + requestedName + "' was found."; return false; } private static bool TryFindPlayer(int actorNumber, out Character? target) { target = null; foreach (Character allCharacter in Character.AllCharacters) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.isBot && !allCharacter.isZombie && !allCharacter.isScoutmaster && !((Object)(object)((MonoBehaviourPun)allCharacter).photonView == (Object)null) && ((MonoBehaviourPun)allCharacter).photonView.OwnerActorNr == actorNumber) { target = allCharacter; return true; } } return false; } private static string NormalizeName(string name) { string text = (name ?? string.Empty).Trim(); while (text.StartsWith("@", StringComparison.Ordinal)) { text = text.Substring(1).TrimStart(); } return text.ToUpperInvariant(); } private static string GetBestName(Character character) { PhotonView photonView = ((MonoBehaviourPun)character).photonView; object obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? owner.NickName : null); } if (obj == null) { obj = string.Empty; } string text = (string)obj; string text2; if (string.IsNullOrWhiteSpace(text)) { text2 = character.characterName; if (text2 == null) { return "Unknown player"; } } else { text2 = text; } return text2; } private static void BroadcastFallProtection(int actorNumber) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_003a: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { object[] array = new object[3] { "com.local.peaktwitchintegration:cannon-launch:v1", actorNumber, 300f }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)173, (object)array, val, SendOptions.SendReliable); } } private static void ShowLocalCountdown(int actorNumber, int seconds, string rewardTitle) { _runtime?.ShowCountdown(actorNumber, seconds, rewardTitle); } private static void BroadcastCountdown(int actorNumber, int seconds, string rewardTitle) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_003a: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { object[] array = new object[4] { "com.local.peaktwitchintegration:cannon-countdown:v1", actorNumber, seconds, rewardTitle }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent((byte)174, (object)array, val, SendOptions.SendReliable); } } private static void OnPhotonEvent(EventData eventData) { if (!(eventData.CustomData is object[] array)) { return; } object obj; if (array.Length == 3 && eventData.Code == 173 && array[0] is string text && text == "com.local.peaktwitchintegration:cannon-launch:v1") { obj = array[1]; if (obj is int) { int protectedActorNumber = (int)obj; obj = array[2]; if (obj is float) { float protectionSeconds = (float)obj; Plugin.QueueMainThreadAction(delegate { FallDamageSafety.Begin(protectedActorNumber, protectionSeconds, "Twitch Cannon Launch"); }); return; } } } if (array.Length == 3 && eventData.Code == 175 && array[0] is string text2 && text2 == "com.local.peaktwitchintegration:teammate-push-protection:v1") { obj = array[1]; if (obj is int) { int pushedActorNumber = (int)obj; obj = array[2]; if (obj is float) { float pushProtectionSeconds = (float)obj; Plugin.QueueMainThreadAction(delegate { FallDamageSafety.Begin(pushedActorNumber, pushProtectionSeconds, "Teammate Push"); }); return; } } } if (array.Length != 4 || eventData.Code != 174 || !(array[0] is string text3) || !(text3 == "com.local.peaktwitchintegration:cannon-countdown:v1")) { return; } obj = array[1]; if (!(obj is int)) { return; } int countdownActorNumber = (int)obj; obj = array[2]; if (!(obj is int)) { return; } int countdownSeconds = (int)obj; obj = array[3]; string rewardTitle = obj as string; if (rewardTitle != null) { Plugin.QueueMainThreadAction(delegate { ShowLocalCountdown(countdownActorNumber, countdownSeconds, rewardTitle); }); } } private static void BeginLocalProtection(int actorNumber) { FallDamageSafety.Begin(actorNumber, 300f, "Twitch Cannon Launch"); } } internal sealed class CannonLaunchRuntime : MonoBehaviour { private readonly List<float> _countdownDeadlines = new List<float>(); private GameObject? _overlayRoot; private TextMeshProUGUI? _countdownLabel; private GameObject? _rewardPopup; private RectTransform? _rewardPopupTransform; private CanvasGroup? _rewardPopupCanvasGroup; private TextMeshProUGUI? _rewardPopupLabel; private int _lastNumber; private float _numberChangedAt; private float _popupShownAt; private float _popupUntil; internal void ShowCountdown(int actorNumber, int seconds, string rewardTitle) { Character localCharacter = Character.localCharacter; if (!((Object)(object)localCharacter == (Object)null) && !((Object)(object)((MonoBehaviourPun)localCharacter).photonView == (Object)null) && ((MonoBehaviourPun)localCharacter).photonView.OwnerActorNr == actorNumber) { EnsureOverlay(); float unscaledTime = Time.unscaledTime; int num = Mathf.Clamp(seconds, 1, 10); _countdownDeadlines.Add(unscaledTime + (float)num); _popupShownAt = unscaledTime; _popupUntil = Mathf.Max(_popupUntil, unscaledTime + (float)num + 1.5f); if ((Object)(object)_rewardPopupLabel != (Object)null) { ((TMP_Text)_rewardPopupLabel).text = "TWITCH REWARD\n" + (rewardTitle ?? "UNKNOWN REWARD").ToUpperInvariant(); } GameObject? rewardPopup = _rewardPopup; if (rewardPopup != null) { rewardPopup.SetActive(true); } GameObject? overlayRoot = _overlayRoot; if (overlayRoot != null) { overlayRoot.SetActive(true); } } } internal void ClearCountdowns() { _countdownDeadlines.Clear(); _lastNumber = 0; _popupUntil = 0f; GameObject? rewardPopup = _rewardPopup; if (rewardPopup != null) { rewardPopup.SetActive(false); } GameObject? overlayRoot = _overlayRoot; if (overlayRoot != null) { overlayRoot.SetActive(false); } } private void Update() { //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_overlayRoot == (Object)null || (Object)(object)_countdownLabel == (Object)null) { return; } float now = Time.unscaledTime; _countdownDeadlines.RemoveAll((float deadline) => deadline <= now); bool flag = _countdownDeadlines.Count > 0; if (!flag) { _lastNumber = 0; ((Component)_countdownLabel).gameObject.SetActive(false); } else { ((Component)_countdownLabel).gameObject.SetActive(true); float num = _countdownDeadlines[0]; for (int num2 = 1; num2 < _countdownDeadlines.Count; num2++) { num = Mathf.Min(num, _countdownDeadlines[num2]); } int num3 = Mathf.Clamp(Mathf.CeilToInt(num - now), 1, 3); if (num3 != _lastNumber) { _lastNumber = num3; _numberChangedAt = now; ((TMP_Text)_countdownLabel).text = num3.ToString(); TextMeshProUGUI countdownLabel = _countdownLabel; ((Graphic)countdownLabel).color = Color32.op_Implicit((Color32)(num3 switch { 3 => new Color32((byte)83, (byte)224, (byte)121, byte.MaxValue), 2 => new Color32(byte.MaxValue, (byte)215, (byte)64, byte.MaxValue), _ => new Color32(byte.MaxValue, (byte)75, (byte)72, byte.MaxValue), })); } float num4 = Mathf.Clamp01((now - _numberChangedAt) / 0.24f); float num5 = Mathf.Lerp(1.28f, 1f, num4); ((Transform)((TMP_Text)_countdownLabel).rectTransform).localScale = new Vector3(num5, num5, 1f); } bool flag2 = now < _popupUntil; GameObject? rewardPopup = _rewardPopup; if (rewardPopup != null) { rewardPopup.SetActive(flag2); } if (flag2 && (Object)(object)_rewardPopupCanvasGroup != (Object)null && (Object)(object)_rewardPopupTransform != (Object)null) { float num6 = Mathf.Clamp01((now - _popupShownAt) / 0.18f); float num7 = Mathf.Clamp01((_popupUntil - now) / 0.35f); _rewardPopupCanvasGroup.alpha = Mathf.Min(num6, num7); _rewardPopupTransform.anchoredPosition = new Vector2(Mathf.Lerp(18f, 42f, num6), 42f); } _overlayRoot.SetActive(flag || flag2); } private void EnsureOverlay() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0383: 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_03c0: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Unknown result type (might be due to invalid IL or missing references) //IL_03e0: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0412: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_045f: Expected O, but got Unknown //IL_047d: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Unknown result type (might be due to invalid IL or missing references) //IL_049d: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_0546: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_overlayRoot != (Object)null)) { TextMeshProUGUI obj = Object.FindAnyObjectByType<TextMeshProUGUI>(); TMP_FontAsset font = ((obj != null) ? ((TMP_Text)obj).font : null) ?? TMP_Settings.defaultFontAsset; _overlayRoot = new GameObject("PeakTwitchIntegration_CannonCountdown", new Type[3] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler) }); _overlayRoot.transform.SetParent(((Component)this).transform, false); Canvas component = _overlayRoot.GetComponent<Canvas>(); component.renderMode = (RenderMode)0; component.sortingOrder = 250; CanvasScaler component2 = _overlayRoot.GetComponent<CanvasScaler>(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; GameObject val = new GameObject("CountdownNumber", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI) }); val.transform.SetParent(_overlayRoot.transform, false); RectTransform component3 = val.GetComponent<RectTransform>(); component3.anchorMin = new Vector2(0.5f, 0.62f); component3.anchorMax = new Vector2(0.5f, 0.62f); component3.pivot = new Vector2(0.5f, 0.5f); component3.anchoredPosition = Vector2.zero; component3.sizeDelta = new Vector2(320f, 170f); _countdownLabel = val.GetComponent<TextMeshProUGUI>(); ((TMP_Text)_countdownLabel).font = font; ((TMP_Text)_countdownLabel).fontSize = 112f; ((TMP_Text)_countdownLabel).fontStyle = (FontStyles)1; ((TMP_Text)_countdownLabel).alignment = (TextAlignmentOptions)514; ((Graphic)_countdownLabel).color = new Color(0.92f, 1f, 0.9f, 1f); ((TMP_Text)_countdownLabel).outlineWidth = 0.22f; ((TMP_Text)_countdownLabel).outlineColor = new Color32((byte)12, (byte)18, (byte)14, byte.MaxValue); ((Graphic)_countdownLabel).raycastTarget = false; ((TMP_Text)_countdownLabel).text = string.Empty; _rewardPopup = new GameObject("TwitchRewardPopup", new Type[4] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup) }); _rewardPopup.transform.SetParent(_overlayRoot.transform, false); _rewardPopupTransform = _rewardPopup.GetComponent<RectTransform>(); _rewardPopupTransform.anchorMin = Vector2.zero; _rewardPopupTransform.anchorMax = Vector2.zero; _rewardPopupTransform.pivot = Vector2.zero; _rewardPopupTransform.anchoredPosition = new Vector2(42f, 42f); _rewardPopupTransform.sizeDelta = new Vector2(430f, 76f); Image component4 = _rewardPopup.GetComponent<Image>(); ((Graphic)component4).color = Color32.op_Implicit(new Color32((byte)12, (byte)18, (byte)14, (byte)224)); ((Graphic)component4).raycastTarget = false; _rewardPopupCanvasGroup = _rewardPopup.GetComponent<CanvasGroup>(); GameObject val2 = new GameObject("Accent", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val2.transform.SetParent(_rewardPopup.transform, false); RectTransform component5 = val2.GetComponent<RectTransform>(); component5.anchorMin = new Vector2(0f, 0f); component5.anchorMax = new Vector2(0f, 1f); component5.pivot = new Vector2(0f, 0.5f); component5.anchoredPosition = Vector2.zero; component5.sizeDelta = new Vector2(7f, 0f); Image component6 = val2.GetComponent<Image>(); ((Graphic)component6).color = Color32.op_Implicit(new Color32((byte)83, (byte)224, (byte)121, byte.MaxValue)); ((Graphic)component6).raycastTarget = false; GameObject val3 = new GameObject("RewardName", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI) }); val3.transform.SetParent(_rewardPopup.transform, false); RectTransform component7 = val3.GetComponent<RectTransform>(); component7.anchorMin = Vector2.zero; component7.anchorMax = Vector2.one; component7.offsetMin = new Vector2(22f, 8f); component7.offsetMax = new Vector2(-14f, -8f); _rewardPopupLabel = val3.GetComponent<TextMeshProUGUI>(); ((TMP_Text)_rewardPopupLabel).font = font; ((TMP_Text)_rewardPopupLabel).fontSize = 22f; ((TMP_Text)_rewardPopupLabel).fontStyle = (FontStyles)1; ((TMP_Text)_rewardPopupLabel).alignment = (TextAlignmentOptions)4097; ((Graphic)_rewardPopupLabel).color = Color32.op_Implicit(new Color32((byte)235, byte.MaxValue, (byte)230, byte.MaxValue)); ((TMP_Text)_rewardPopupLabel).outlineWidth = 0.12f; ((TMP_Text)_rewardPopupLabel).outlineColor = new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue); ((Graphic)_rewardPopupLabel).raycastTarget = false; ((TMP_Text)_rewardPopupLabel).text = string.Empty; _rewardPopup.SetActive(false); _overlayRoot.SetActive(false); } } private void OnDestroy() { if ((Object)(object)_overlayRoot != (Object)null) { Object.Destroy((Object)(object)_overlayRoot); } } } internal static class FallDamageSafety { private static int _protectedActorNumber = -1; private static float _protectedUntil; private static string _reason = "mod movement effect"; internal static string Reason => _reason; internal static void Begin(int actorNumber, float seconds, string reason) { Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || (Object)(object)((MonoBehaviourPun)localCharacter).photonView == (Object)null || ((MonoBehaviourPun)localCharacter).photonView.OwnerActorNr != actorNumber || (Object)(object)localCharacter.data == (Object)null) { return; } float num = Time.unscaledTime + Mathf.Clamp(seconds, 1f, 300f); int num2; if (_protectedActorNumber == actorNumber) { num2 = ((Time.unscaledTime <= _protectedUntil) ? 1 : 0); if (num2 != 0 && !(num >= _protectedUntil)) { goto IL_008b; } } else { num2 = 0; } _reason = (string.IsNullOrWhiteSpace(reason) ? "mod movement effect" : reason); goto IL_008b; IL_008b: _protectedActorNumber = actorNumber; _protectedUntil = ((num2 != 0) ? Mathf.Max(_protectedUntil, num) : num); localCharacter.data.launchedByCannon = true; } internal static bool IsProtected(Character? character) { if ((Object)(object)character == (Object)null || !character.IsLocal || (Object)(object)((MonoBehaviourPun)character).photonView == (Object)null || ((MonoBehaviourPun)character).photonView.OwnerActorNr != _protectedActorNumber) { return false; } if (Time.unscaledTime <= _protectedUntil) { return true; } Clear(); return false; } internal static void OnLanded(Character? character) { if (IsProtected(character)) { if ((Object)(object)character != (Object)null && (Object)(object)character.data != (Object)null) { character.data.launchedByCannon = false; character.data.fallSeconds = 0f; } Clear(); } } internal static void Clear() { Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.data != (Object)null && (Object)(object)((MonoBehaviourPun)localCharacter).photonView != (Object)null && ((MonoBehaviourPun)localCharacter).photonView.OwnerActorNr == _protectedActorNumber) { localCharacter.data.launchedByCannon = false; } _protectedActorNumber = -1; _protectedUntil = 0f; _reason = "mod movement effect"; } } [HarmonyPatch(typeof(CharacterMovement), "CheckFallDamage")] internal static class ModFallDamagePatch { private static bool Prefix(Character ___character) { if (!FallDamageSafety.IsProtected(___character)) { return true; } Plugin.Log.LogInfo((object)("Prevented fall damage caused by " + FallDamageSafety.Reason + ".")); return false; } } [HarmonyPatch(typeof(CharacterMovement), "Land")] internal static class ModEffectLandingPatch { private static void Prefix(Character ___character, out bool __state) { __state = (Object)(object)___character != (Object)null && (Object)(object)___character.data != (Object)null && ___character.data.sinceGrounded > 0.5f && FallDamageSafety.IsProtected(___character); } private static void Postfix(Character ___character, bool __state) { if (__state) { FallDamageSafety.OnLanded(___character); } } } internal static class CredentialProtector { private struct DataBlob { internal int Size; internal IntPtr Data; } private const int CryptProtectUiForbidden = 1; [DllImport("Crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CryptProtectData(ref DataBlob dataIn, string description, IntPtr optionalEntropy, IntPtr reserved, IntPtr prompt, int flags, out DataBlob dataOut); [DllImport("Crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CryptUnprotectData(ref DataBlob dataIn, out IntPtr description, IntPtr optionalEntropy, IntPtr reserved, IntPtr prompt, int flags, out DataBlob dataOut); [DllImport("Kernel32.dll")] private static extern IntPtr LocalFree(IntPtr memory); internal static string Protect(string plaintext) { return Convert.ToBase64String(Transform(Encoding.UTF8.GetBytes(plaintext), protect: true)); } internal static string Unprotect(string protectedBase64) { byte[] bytes = Transform(Convert.FromBase64String(protectedBase64), protect: false); return Encoding.UTF8.GetString(bytes); } private static byte[] Transform(byte[] input, bool protect) { DataBlob dataIn = default(DataBlob); DataBlob dataOut = default(DataBlob); IntPtr description = IntPtr.Zero; try { dataIn.Size = input.Length; dataIn.Data = Marshal.AllocHGlobal(Math.Max(input.Length, 1)); if (input.Length != 0) { Marshal.Copy(input, 0, dataIn.Data, input.Length); } if (!(protect ? CryptProtectData(ref dataIn, "PEAK Twitch Integration OAuth credentials", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 1, out dataOut) : CryptUnprotectData(ref dataIn, out description, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 1, out dataOut))) { throw new Win32Exception(Marshal.GetLastWin32Error()); } byte[] array = new byte[dataOut.Size]; if (array.Length != 0) { Marshal.Copy(dataOut.Data, array, 0, array.Length); } return array; } finally { if (dataIn.Data != IntPtr.Zero) { Marshal.FreeHGlobal(dataIn.Data); } if (dataOut.Data != IntPtr.Zero) { LocalFree(dataOut.Data); } if (description != IntPtr.Zero) { LocalFree(description); } } } } internal static class DeveloperDummyHudFactory { private const float VerticalOffset = 72f; internal static GameObject? TryCreate(Character target, string displayName) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: 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) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: 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) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0244: 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_0269: Unknown result type (might be due to invalid IL or missing references) StaminaBar val = GUIManager.instance?.bar; if ((Object)(object)target == (Object)null || (Object)(object)val == (Object)null || (Object)(object)((Component)val).transform.parent == (Object)null) { return null; } GameObject val2 = null; try { val2 = new GameObject("PeakTwitchIntegration_DeveloperDummyHud", new Type[1] { typeof(RectTransform) }); RectTransform component = val2.GetComponent<RectTransform>(); ((Transform)component).SetParent(((Component)val).transform.parent, false); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.pivot = new Vector2(0.5f, 0.5f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; ((Transform)component).SetSiblingIndex(((Component)val).transform.GetSiblingIndex() + 1); GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, (Transform)(object)component, false); ((Object)val3).name = "Developer Dummy Stamina Bar"; RectTransform component2 = val3.GetComponent<RectTransform>(); component2.anchoredPosition += new Vector2(0f, 72f); if ((Object)(object)val3.GetComponent<StaminaBar>() == (Object)null) { Object.Destroy((Object)(object)val2); return null; } GameObject val4 = new GameObject("Developer Dummy Status Label", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI) }); val4.transform.SetParent(val3.transform, false); RectTransform component3 = val4.GetComponent<RectTransform>(); component3.anchorMin = new Vector2(0f, 1f); component3.anchorMax = new Vector2(0f, 1f); component3.pivot = new Vector2(0f, 0f); component3.anchoredPosition = new Vector2(0f, 5f); component3.sizeDelta = new Vector2(520f, 30f); TextMeshProUGUI component4 = val4.GetComponent<TextMeshProUGUI>(); Canvas componentInParent = ((Component)val).GetComponentInParent<Canvas>(); object obj; if (componentInParent == null) { obj = null; } else { TextMeshProUGUI componentInChildren = ((Component)componentInParent).GetComponentInChildren<TextMeshProUGUI>(true); obj = ((componentInChildren != null) ? ((TMP_Text)componentInChildren).font : null); } if (obj == null) { TextMeshProUGUI obj2 = Object.FindAnyObjectByType<TextMeshProUGUI>(); obj = ((obj2 != null) ? ((TMP_Text)obj2).font : null) ?? TMP_Settings.defaultFontAsset; } ((TMP_Text)component4).font = (TMP_FontAsset)obj; ((TMP_Text)component4).fontSize = 18f; ((TMP_Text)component4).fontStyle = (FontStyles)1; ((TMP_Text)component4).alignment = (TextAlignmentOptions)1025; ((Graphic)component4).color = Color32.op_Implicit(DeveloperDummyHudMarker.NormalColor); ((TMP_Text)component4).outlineWidth = 0.14f; ((TMP_Text)component4).outlineColor = new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue); ((Graphic)component4).raycastTarget = false; val3.AddComponent<DeveloperDummyHudMarker>().Initialize(target, component4, displayName); return val2; } catch { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } throw; } } } internal sealed class DeveloperDummyHudMarker : MonoBehaviour { internal static readonly Color32 NormalColor = new Color32((byte)235, byte.MaxValue, (byte)230, byte.MaxValue); private static readonly Color32 DamageColor = new Color32(byte.MaxValue, (byte)82, (byte)72, byte.MaxValue); private Character? _target; private TextMeshProUGUI? _label; private string _displayName = "PUSH TEST DUMMY"; private float _lastInjury; private float _damageAmount; private float _damageMessageUntil; internal Character? Target => _target; internal void Initialize(Character target, TextMeshProUGUI label, string displayName) { _target = target; _label = label; _displayName = (string.IsNullOrWhiteSpace(displayName) ? "PUSH TEST DUMMY" : displayName); _lastInjury = ReadInjury(); RefreshLabel(); } private void Update() { if ((Object)(object)_target == (Object)null || (Object)(object)_target.data == (Object)null || (Object)(object)_target.refs?.afflictions == (Object)null) { if ((Object)(object)((Component)this).gameObject != (Object)null) { ((Component)this).gameObject.SetActive(false); } return; } float num = ReadInjury(); if (num > _lastInjury + 0.0001f) { _damageAmount = num - _lastInjury; _damageMessageUntil = Time.unscaledTime + 2.75f; } _lastInjury = num; RefreshLabel(); } private float ReadInjury() { Character? target = _target; float? obj; if (target == null) { obj = null; } else { CharacterRefs refs = target.refs; if (refs == null) { obj = null; } else { CharacterAfflictions afflictions = refs.afflictions; obj = ((afflictions != null) ? new float?(afflictions.GetCurrentStatus((STATUSTYPE)0)) : ((float?)null)); } } float? num = obj; return num.GetValueOrDefault(); } private void RefreshLabel() { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_label == (Object)null) && !((Object)(object)_target == (Object)null) && !((Object)(object)_target.data == (Object)null)) { int num = Mathf.RoundToInt(Mathf.Clamp01(_target.data.currentStamina) * 100f); int num2 = Mathf.RoundToInt(Mathf.Clamp01(_lastInjury) * 100f); if (Time.unscaledTime < _damageMessageUntil) { int num3 = Mathf.Max(1, Mathf.RoundToInt(_damageAmount * 100f)); ((TMP_Text)_label).text = $"{_displayName} DAMAGE +{num3}% INJURY {num2}%"; ((Graphic)_label).color = Color32.op_Implicit(DamageColor); } else { string text = (_target.data.dead ? "DEAD" : (_target.data.fullyPassedOut ? "PASSED OUT" : $"STAMINA {num}% INJURY {num2}%")); ((TMP_Text)_label).text = _displayName + " " + text; ((Graphic)_label).color = Color32.op_Implicit(NormalColor); } } } } internal static class DeveloperDummyHudContext { private static Character? _target; internal static Character? Target => _target; internal static bool Begin(StaminaBar bar) { DeveloperDummyHudMarker component = ((Component)bar).GetComponent<DeveloperDummyHudMarker>(); if ((Object)(object)component == (Object)null || (Object)(object)component.Target == (Object)null) { return false; } _target = component.Target; bar.ChangeBar(); return true; } internal static void End() { _target = null; } } [HarmonyPatch(typeof(StaminaBar), "Update")] internal static class DeveloperDummyStaminaBarUpdatePatch { private static void Prefix(StaminaBar __instance, out bool __state) { __state = DeveloperDummyHudContext.Begin(__instance); } private static Exception? Finalizer(Exception? __exception, bool __state) { if (__state) { DeveloperDummyHudContext.End(); } return __exception; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class DeveloperDummyObservedCharacterPatch { private static void Postfix(ref Character __result) { Character target = DeveloperDummyHudContext.Target; if ((Object)(object)target != (Object)null) { __result = target; } } } internal sealed class DeveloperToolsController : MonoBehaviour { private const string DummyName = "[DEV] PUSH TEST DUMMY"; private readonly List<GameObject> _spawnedRoots = new List<GameObject>(); private Character? _activeDummy; private GameObject? _dummyHudRoot; private string _status = "Enable the test-bot spawner, then press the configured key."; internal string Status => _status; internal bool CanUseDeveloperTools { get { if (PhotonNetwork.InRoom) { return PhotonNetwork.IsMasterClient; } return true; } } internal string AccessStatus { get { if (!CanUseDeveloperTools) { return "HOST ONLY: The lobby host controls the test-bot spawner."; } if (!PhotonNetwork.InRoom) { return "Available outside a multiplayer room and to the lobby host."; } return "Host access confirmed. Only you can spawn the local test bot."; } } internal int ActiveDummyCount { get { RemoveMissingReferences(); return _spawnedRoots.Count; } } private void Update() { //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!CanUseDeveloperTools) { if (_spawnedRoots.Count > 0 || (Object)(object)_activeDummy != (Object)null || (Object)(object)_dummyHudRoot != (Object)null) { RemoveTestBots(); } _status = "Test-bot spawning is locked to the lobby host."; } else if (Plugin.DeveloperToolsEnabled) { RemoveMissingReferences(); if ((Object)(object)_activeDummy != (Object)null && (Object)(object)_dummyHudRoot == (Object)null) { _dummyHudRoot = TryCreateDummyHud(_activeDummy); } Keyboard current = Keyboard.current; if (current != null && ((ButtonControl)current[Plugin.DeveloperTestBotSpawnKey]).wasPressedThisFrame) { SpawnOrReplaceTestBot(); } } } internal void SpawnOrReplaceTestBot() { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) if (!CanUseDeveloperTools) { _status = "Test-bot spawning is locked to the lobby host."; Plugin.Log.LogWarning((object)_status); return; } if (!Plugin.DeveloperToolsEnabled) { _status = "Test-bot spawning is disabled."; return; } Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead || localCharacter.data.fullyPassedOut) { _status = "Enter a playable PEAK level before spawning a test bot."; return; } BotSpawner val = FindBotSpawner(); if ((Object)(object)val == (Object)null || (Object)(object)val.botPrefab == (Object)null) { _status = "This level has no loaded PEAK bot prefab."; Plugin.Log.LogWarning((object)_status); return; } RemoveTestBots(); try { Vector3 val2 = FindSpawnPosition(localCharacter); GameObject val3 = Object.Instantiate<GameObject>(val.botPrefab, val2, Quaternion.identity); ((Object)val3).name = "PEAK Developer Push Test Dummy"; Character componentInChildren = val3.GetComponentInChildren<Character>(true); if ((Object)(object)componentInChildren == (Object)null) { Object.Destroy((Object)(object)val3); _status = "PEAK's bot prefab did not contain a Character component."; Plugin.Log.LogWarning((object)_status); return; } (((Component)componentInChildren).gameObject.GetComponent<DeveloperTestBotMarker>() ?? ((Component)componentInChildren).gameObject.AddComponent<DeveloperTestBotMarker>()).SpawnedRoot = val3; componentInChildren.isBot = true; componentInChildren.isZombie = false; componentInChildren.isScoutmaster = false; AccessTools.Field(typeof(Character), "<characterName>k__BackingField")?.SetValue(componentInChildren, "[DEV] PUSH TEST DUMMY"); DisableBotControl(val3); _spawnedRoots.Add(val3); _activeDummy = componentInChildren; _dummyHudRoot = TryCreateDummyHud(componentInChildren); _status = (((Object)(object)_dummyHudRoot != (Object)null) ? string.Format("Spawned {0} with a live damage/status bar. Look at it and press {1}.", "[DEV] PUSH TEST DUMMY", Plugin.TeammatePushKey) : "Spawned [DEV] PUSH TEST DUMMY. Its damage/status bar will appear when PEAK's HUD is ready."); Plugin.Log.LogInfo((object)_status); } catch (Exception arg) { _status = "Could not spawn the PEAK test bot. See the BepInEx log."; Plugin.Log.LogError((object)$"Developer test-bot spawn failed: {arg}"); } } internal void RemoveTestBots() { RemoveDummyHud(); _activeDummy = null; foreach (GameObject spawnedRoot in _spawnedRoots) { if ((Object)(object)spawnedRoot != (Object)null) { Object.Destroy((Object)(object)spawnedRoot); } } _spawnedRoots.Clear(); DeveloperTestBotMarker[] array = Resources.FindObjectsOfTypeAll<DeveloperTestBotMarker>(); foreach (DeveloperTestBotMarker developerTestBotMarker in array) { if (!((Object)(object)developerTestBotMarker == (Object)null)) { GameObject val = (GameObject)(((Object)(object)developerTestBotMarker.SpawnedRoot != (Object)null) ? ((object)developerTestBotMarker.SpawnedRoot) : ((object)((Component)developerTestBotMarker).gameObject)); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } _status = "Removed all developer test bots."; } private static GameObject? TryCreateDummyHud(Character target) { try { return DeveloperDummyHudFactory.TryCreate(target, "[DEV] PUSH TEST DUMMY"); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"Developer dummy HUD creation failed: {arg}"); return null; } } private void RemoveDummyHud() { if ((Object)(object)_dummyHudRoot != (Object)null) { Object.Destroy((Object)(object)_dummyHudRoot); } _dummyHudRoot = null; } private static BotSpawner? FindBotSpawner() { BotSpawner val = Object.FindAnyObjectByType<BotSpawner>(); if ((Object)(object)val != (Object)null && (Object)(object)val.botPrefab != (Object)null) { return val; } return ((IEnumerable<BotSpawner>)Resources.FindObjectsOfTypeAll<BotSpawner>()).FirstOrDefault((Func<BotSpawner, bool>)((BotSpawner candidate) => (Object)(object)candidate != (Object)null && (Object)(object)candidate.botPrefab != (Object)null)); } private static Vector3 FindSpawnPosition(Character local) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_004b: 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_00b8: 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_00f9: 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_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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) Vector3 val = Vector3.ProjectOnPlane(((Component)local).transform.forward, Vector3.up); if ((Object)(object)MainCamera.instance != (Object)null) { val = Vector3.ProjectOnPlane(((Component)MainCamera.instance).transform.forward, Vector3.up); } if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); Vector3 val2 = local.Center + val; RaycastHit[] array = Physics.RaycastAll(val2 + Vector3.up * 3f, Vector3.down, 10f, -5, (QueryTriggerInteraction)1); Array.Sort(array, (RaycastHit left, RaycastHit right) => ((RaycastHit)(ref left)).distance.CompareTo(((RaycastHit)(ref right)).distance)); RaycastHit[] array2 = array; for (int num = 0; num < array2.Length; num++) { RaycastHit val3 = array2[num]; if (!((Object)(object)((Component)((RaycastHit)(ref val3)).collider).GetComponentInParent<Character>() == (Object)(object)local)) { return ((RaycastHit)(ref val3)).point + Vector3.up * 0.2f; } } return val2 + Vector3.up * 0.5f; } private static void DisableBotControl(GameObject spawnedRoot) { MonoBehaviour[] componentsInChildren = spawnedRoot.GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !(val is DeveloperTestBotMarker)) { string name = ((object)val).GetType().Name; if (val is Bot || val is BotToCharacterTranslator || val is BotMoverRagdoll || name.StartsWith("Bot", StringComparison.Ordinal) || name == "Navigator") { ((Behaviour)val).enabled = false; } } } } private void RemoveMissingReferences() { _spawnedRoots.RemoveAll((GameObject root) => (Object)(object)root == (Object)null); if ((Object)(object)_activeDummy == (Object)null) { _activeDummy = null; RemoveDummyHud(); } } private void OnDestroy() { RemoveTestBots(); } } internal sealed class DeveloperTestBotMarker : MonoBehaviour { internal GameObject? SpawnedRoot { get; set; } } internal sealed class IncomingVoiceProcessor : IDisposable { private readonly object _sync = new object(); private readonly int _sampleRate; private readonly int _channels; private readonly LoudnessLeveler _leveler; private RnNoiseDenoiser? _denoiser; private bool _disposed; internal IncomingVoiceProcessor(int sampleRate, int channels) { _sampleRate = Math.Max(sampleRate, 1); _channels = Math.Max(channels, 1); _leveler = new LoudnessLeveler(_sampleRate, _channels); try { _denoiser = new RnNoiseDenoiser(_sampleRate, _channels); } catch (Exception ex) { Plugin.Log.LogError((object)("RNNoise could not be initialized. Loudness leveling will still work. " + ex.Message)); } } internal void Process(float[] samples, ProcessorSettings settings) { lock (_sync) { if (!_disposed && settings.Enabled) { float voiceProbability = -1f; if (settings.NoiseSuppression && _denoiser != null) { voiceProbability = _denoiser.Process(samples, settings.SuppressionStrength); } if (settings.AutoLevel) { _leveler.Process(samples, settings, voiceProbability); } else { _leveler.Reset(); } } } } public void Dispose() { lock (_sync) { if (!_disposed) { _disposed = true; _denoiser?.Dispose(); _denoiser = null; } } } } internal readonly struct ProcessorSettings { internal bool Enabled { get; } internal bool NoiseSuppression { get; } internal float SuppressionStrength { get; } internal bool AutoLevel { get; } internal float TargetRmsDb { get; } internal float EqualizedVolumePercent { get; } internal float MinimumGainDb { get; } internal float MaximumGainDb { get; } internal float SpeechGateDb { get; } internal ProcessorSettings(bool enabled, bool noiseSuppression, float suppressionStrength, bool autoLevel, float targetRmsDb, float equalizedVolumePercent, float minimumGainDb, float maximumGainDb, float speechGateDb) { Enabled = enabled; NoiseSuppression = noiseSuppression; SuppressionStrength = Clamp(suppressionStrength, 0f, 1f); AutoLevel = autoLevel; TargetRmsDb = Clamp(targetRmsDb, -30f, -10f); EqualizedVolumePercent = Clamp(equalizedVolumePercent, 0f, 200f); MinimumGainDb = Clamp(minimumGainDb, -30f, 0f); MaximumGainDb = Clamp(maximumGainDb, 0f, 30f); SpeechGateDb = Clamp(speechGateDb, -80f, -30f); } private static float Clamp(float value, float minimum, float maximum) { if (!(value < minimum)) { if (!(value > maximum)) { return value; } return maximum; } return minimum; } } internal sealed class LoudnessLeveler { private const float Epsilon = 1E-07f; private const float LimiterCeiling = 0.96f; private const float VoiceProbabilityThreshold = 0.45f; private readonly int _sampleRate; private readonly int _channels; private float _estimatedSpeechDb; private float _gainDb; private float _limiterGain = 1f; private bool _hasSpeechEstimate; internal float CurrentGainDb => _gainDb + LinearToDb(_limiterGain); internal LoudnessLeveler(int sampleRate, int channels) { _sampleRate = Math.Max(sampleRate, 1); _channels = Math.Max(channels, 1); } internal void Process(float[] samples, ProcessorSettings settings, float voiceProbability) { if (samples.Length == 0) { return; } double num = 0.0; foreach (float num2 in samples) { num += (double)(num2 * num2); } float num3 = LinearToDb((float)Math.Sqrt(num / (double)samples.Length)); float durationSeconds = (float)samples.Length / (float)(_sampleRate * _channels); if (voiceProbability >= 0.45f || num3 >= settings.SpeechGateDb) { if (!_hasSpeechEstimate) { _estimatedSpeechDb = num3; _hasSpeechEstimate = true; } else { float timeConstantSeconds = ((num3 > _estimatedSpeechDb) ? 0.08f : 0.9f); float num4 = TimeBlend(durationSeconds, timeConstantSeconds); _estimatedSpeechDb += (num3 - _estimatedSpeechDb) * num4; } float num5 = Clamp(settings.TargetRmsDb - _estimatedSpeechDb, settings.MinimumGainDb, settings.MaximumGainDb); float timeConstantSeconds2 = ((num5 < _gainDb) ? 0.05f : 0.6f); float num6 = TimeBlend(durationSeconds, timeConstantSeconds2); _gainDb += (num5 - _gainDb) * num6; } float num7 = DbToLinear(_gainDb); float num8 = settings.EqualizedVolumePercent / 100f; float num9 = num7 * num8; float num10 = 0f; for (int j = 0; j < samples.Length; j++) { num10 = Math.Max(num10, Math.Abs(samples[j]) * num9); } float num11 = ((num10 > 0.96f) ? (0.96f / num10) : 1f); if (num11 < _limiterGain) { _limiterGain = num11; } else { float num12 = TimeBlend(durationSeconds, 0.25f); _limiterGain += (num11 - _limiterGain) * num12; } float num13 = num9 * _limiterGain; for (int k = 0; k < samples.Length; k++) { samples[k] = Clamp(samples[k] * num13, -1f, 1f); } } internal void Reset() { _estimatedSpeechDb = 0f; _gainDb = 0f; _limiterGain = 1f; _hasSpeechEstimate = false; } private static float TimeBlend(float durationSeconds, float timeConstantSeconds) { return 1f - (float)Math.Exp((0f - durationSeconds) / Math.Max(timeConstantSeconds, 0.001f)); } private static float DbToLinear(float decibels) { return (float)Math.Pow(10.0, (double)decibels / 20.0); } private static float LinearToDb(float linear) { return 20f * (float)Math.Log10(Math.Max(linear, 1E-07f)); } private static float Clamp(float value, float minimum, float maximum) { if (!(value < minimum)) { if (!(value > maximum)) { return value; } return maximum; } return minimum; } } [HarmonyPatch(typeof(MainMenu), "Start")] internal static class MainMenuTwitchIntegrationPatch { private const string RootName = "PeakTwitchIntegration_Menu"; [HarmonyPostfix] private static void Postfix(MainMenu __instance) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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) if ((Object)(object)__instance == (Object)null) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (!((Scene)(ref scene)).IsValid() || HasMenu(scene)) { return; } try { AddMenu(scene); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not add the PEAK Twitch Integration menu: {arg}"); } } private static bool HasMenu(Scene scene) { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); for (int i = 0; i < rootGameObjects.Length; i++) { if (((Object)rootGameObjects[i]).name == "PeakTwitchIntegration_Menu") { return true; } } return false; } private static void AddMenu(Scene menuScene) { //IL_001f: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0186: 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_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_021e: 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_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) Texture2D val = CreateTwitchLogoTexture(); Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = "PEAK Twitch logo"; GameObject val3 = new GameObject("PeakTwitchIntegration_Menu", new Type[6] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(TwitchLogoAssetOwner), typeof(TwitchIntegrationMenuController) }); SceneManager.MoveGameObjectToScene(val3, menuScene); Canvas component = val3.GetComponent<Canvas>(); component.renderMode = (RenderMode)0; component.sortingOrder = 100; CanvasScaler component2 = val3.GetComponent<CanvasScaler>(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; TwitchLogoAssetOwner component3 = val3.GetComponent<TwitchLogoAssetOwner>(); component3.Texture = val; component3.Sprite = val2; GameObject val4 = new GameObject("OpenTwitchIntegration", new Type[4] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button) }); val4.transform.SetParent(val3.transform, false); RectTransform component4 = val4.GetComponent<RectTransform>(); component4.anchorMin = new Vector2(1f, 1f); component4.anchorMax = new Vector2(1f, 1f); component4.pivot = new Vector2(1f, 1f); component4.anchoredPosition = new Vector2(-22f, -22f); component4.sizeDelta = new Vector2(64f, 64f); Image component5 = val4.GetComponent<Image>(); component5.sprite = val2; component5.preserveAspect = true; ((Graphic)component5).raycastTarget = true; Button component6 = val4.GetComponent<Button>(); ((Selectable)component6).targetGraphic = (Graphic)(object)component5; ColorBlock colors = ((Selectable)component6).colors; ((ColorBlock)(ref colors)).normalColor = new Color(1f, 1f, 1f, 0.82f); ((ColorBlock)(ref colors)).highlightedColor = Color.white; ((ColorBlock)(ref colors)).pressedColor = new Color(0.78f, 0.68f, 0.96f, 1f); ((ColorBlock)(ref colors)).selectedColor = ((ColorBlock)(ref colors)).highlightedColor; ((ColorBlock)(ref colors)).fadeDuration = 0.08f; ((Selectable)component6).colors = colors; val3.GetComponent<TwitchIntegrationMenuController>().Initialize(component6, val4); Plugin.Log.LogInfo((object)"Added the compact Twitch logo button to the main menu."); } private static Texture2D CreateTwitchLogoTexture() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: 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_0101: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown Color32 value = default(Color32); ((Color32)(ref value))..ctor((byte)0, (byte)0, (byte)0, (byte)0); Color32 color = default(Color32); ((Color32)(ref color))..ctor((byte)145, (byte)70, byte.MaxValue, byte.MaxValue); Color32 color2 = default(Color32); ((Color32)(ref color2))..ctor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); Color32[] array = (Color32[])(object)new Color32[65536]; Array.Fill(array, value); Vector2[] polygon = (Vector2[])(object)new Vector2[8] { new Vector2(20f, 236f), new Vector2(228f, 236f), new Vector2(228f, 88f), new Vector2(180f, 40f), new Vector2(124f, 40f), new Vector2(88f, 4f), new Vector2(88f, 40f), new Vector2(20f, 40f) }; Vector2[] polygon2 = (Vector2[])(object)new Vector2[8] { new Vector2(48f, 208f), new Vector2(200f, 208f), new Vector2(200f, 104f), new Vector2(164f, 68f), new Vector2(112f, 68f), new Vector2(76f, 32f), new Vector2(76f, 68f), new Vector2(48f, 68f) }; FillPolygon(array, 256, polygon, color); FillPolygon(array, 256, polygon2, color2); FillRectangle(array, 256, 88, 112, 112, 176, color); FillRectangle(array, 256, 144, 112, 168, 176, color); Texture2D val = new Texture2D(256, 256, (TextureFormat)4, false) { name = "PEAK Twitch logo", filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; val.SetPixels32(array); val.Apply(false, true); return val; } private static void FillPolygon(Color32[] pixels, int textureSize, Vector2[] polygon, Color32 color) { //IL_0026: 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) for (int i = 0; i < textureSize; i++) { for (int j = 0; j < textureSize; j++) { if (ContainsPoint(polygon, (float)j + 0.5f, (float)i + 0.5f)) { pixels[i * textureSize + j] = color; } } } } private static bool ContainsPoint(Vector2[] polygon, float x, float y) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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) bool flag = false; int num = 0; int num2 = polygon.Length - 1; while (num < polygon.Length) { Vector2 val = polygon[num]; Vector2 val2 = polygon[num2]; if (val.y > y != val2.y > y && x < (val2.x - val.x) * (y - val.y) / (val2.y - val.y) + val.x) { flag = !flag; } num2 = num++; } return flag; } private static void FillRectangle(Color32[] pixels, int textureSize, int left, int bottom, int right, int top, Color32 color) { //IL_000e: 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) for (int i = bottom; i < top; i++) { for (int j = left; j < right; j++) { pixels[i * textureSize + j] = color; } } } } [HarmonyPatch(typeof(PauseMenuMainPage), "Start")] internal static class PauseMenuTwitchIntegrationPatch { private const string ButtonName = "PeakTwitchIntegration_PauseMenuButton"; [HarmonyPostfix] private static void Postfix(PauseMenuMainPage __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).GetComponent<TwitchIntegrationMenuController>() != (Object)null) { return; } try { object? obj = AccessTools.Field(typeof(PauseMenuMainPage), "m_settingsButton")?.GetValue(__instance); Button val = (Button)((obj is Button) ? obj : null); if (val == null) { val = __instance.resumeButton; } if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).transform.parent == (Object)null) { Plugin.Log.LogWarning((object)"Could not add the Twitch Integration button because the pause menu had no usable button template."); return; } GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent, false); ((Object)val2).name = "PeakTwitchIntegration_PauseMenuButton"; val2.transform.SetSiblingIndex(Mathf.Min(((Component)val).transform.GetSiblingIndex() + 1, val2.transform.parent.childCount - 1)); LocalizedText[] componentsInChildren = val2.GetComponentsInChildren<LocalizedText>(true); for (int i = 0; i < componentsInChildren.Length; i++) { ((Behaviour)componentsInChildren[i]).enabled = false; } TMP_Text[] componentsInChildren2 = val2.GetComponentsInChildren<TMP_Text>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].text = "TWITCH INTEGRATION"; } Button component = val2.GetComponent<Button>(); ((UnityEventBase)component.onClick).RemoveAllListeners(); ((Selectable)component).interactable = true; ConfigureNavigation(val, component); ((Component)__instance).gameObject.AddComponent<TwitchIntegrationMenuController>().Initialize(component, val2); val2.SetActive(true); Plugin.Log.LogInfo((object)"Added the Twitch Integration button to the pause menu."); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not add the PEAK Twitch Integration button to the pause menu: {arg}"); } } private static void ConfigureNavigation(Button template, Button button) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_0021: 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) //IL_0034: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Invalid comparison between Unknown and I4 //IL_006c: Unknown result type (might be due to invalid IL or missing references) Navigation navigation = ((Selectable)template).navigation; if ((int)((Navigation)(ref navigation)).mode != 4) { ((Selectable)button).navigation = navigation; return; } Selectable selectOnDown = ((Navigation)(ref navigation)).selectOnDown; Navigation navigation2 = navigation; ((Navigation)(ref navigation2)).selectOnUp = (Selectable)(object)template; ((Navigation)(ref navigation2)).selectOnDown = selectOnDown; ((Selectable)button).navigation = navigation2; ((Navigation)(ref navigation)).selectOnDown = (Selectable)(object)button; ((Selectable)template).navigation = navigation; if ((Object)(object)selectOnDown != (Object)null) { Navigation navigation3 = selectOnDown.navigation; if ((int)((Navigation)(ref navigation3)).mode == 4) { ((Navigation)(ref navigation3)).selectOnUp = (Selectable)(object)button; selectOnDown.navigation = navigation3; } } } } [HarmonyPatch(typeof(PauseMenuHandler), "Update")] internal static class PauseMenuHandlerUpdatePatch { [HarmonyPrefix] private static bool Prefix() { return !TwitchIntegrationMenuController.ShouldBlockPauseMenuUpdate; } } internal sealed class TwitchIntegrationMenuController : MonoBehaviour { private enum MenuTab { Integration, DeveloperTools } private const int WindowIdBase = 781245; private static int _openWindowCount; private static int _suppressPauseMenuThroughFrame = -1; private Button _openButton; private GameObject _openLabel; private bool _windowOpen; private bool _initialized; private string _clientIdDraft = string.Empty; private string _cannonCostDraft = string.Empty; private Rect _windowRect; private Vector2 _scroll; private MenuTab _selectedTab; private Texture2D? _windowBackground; private Texture2D? _panelBackground; private GUIStyle? _windowStyle; private GUIStyle? _panelStyle; private GUIStyle? _sectionHeaderStyle; internal static bool ShouldBlockPauseMenuUpdate { get { if (_openWindowCount <= 0) { return Time.frameCount <= _suppressPauseMenuThroughFrame; } return true; } } internal void Initialize(Button openButton, GameObject openLabel) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown _openButton = openButton; _openLabel = openLabel; ((UnityEvent)_openButton.onClick).AddListener(new UnityAction(ToggleWindow)); _clientIdDraft = Plugin.TwitchClientId; _cannonCostDraft = Plugin.CannonLaunchCost.ToString(); _initialized = true; } private void ToggleWindow() { SetWindowOpen(!_windowOpen); } private void SetWindowOpen(bool open) { if (_windowOpen != open) { _windowOpen = open; _openWindowCount = Mathf.Max(0, _openWindowCount + (open ? 1 : (-1))); if ((Object)(object)_openButton != (Object)null) { ((Component)_openButton).gameObject.SetActive(!open); } if ((Object)(object)_openLabel != (Object)null) { _openLabel.SetActive(!open); } if (open) { CenterWindow(); } } } private void Update() { if (_windowOpen && Input.GetKeyDown((KeyCode)27)) { _suppressPauseMenuThroughFrame = Time.frameCount; SetWindowOpen(open: false); } } private void OnGUI() { //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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if (_windowOpen) { if (((Rect)(ref _windowRect)).width <= 0f || ((Rect)(ref _windowRect)).height <= 0f) { CenterWindow(); } EnsureStyles(); GUI.depth = -1000; Color color = GUI.color; Color contentColor = GUI.contentColor; Color backgroundColor = GUI.backgroundColor; GUI.color = Color.white; GUI.contentColor = new Color(0.94f, 1f, 0.97f, 1f); GUI.backgroundColor = new Color(0.3f, 0.72f, 0.52f, 1f); _windowRect = GUI.Window(0xBEBBD ^ ((Object)this).GetInstanceID(), _windowRect, new WindowFunction(DrawWindow), "PEAK TWITCH INTEGRATION", _windowStyle); GUI.color = color; GUI.contentColor = contentColor; GUI.backgroundColor = backgroundColor; } } private void EnsureStyles() { //IL_001a: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_00a9: 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_00d3: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Expected O, but got Unknown //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Expected O, but got Unknown //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Expected O, but got Unknown if (_windowStyle == null) { _windowBackground = MakeSolidTexture("PEAK Twitch Integration opaque window", new Color32((byte)16, (byte)31, (byte)27, byte.MaxValue)); _panelBackground = MakeSolidTexture("PEAK Twitch Integration panel", new Color32((byte)28, (byte)53, (byte)45, byte.MaxValue)); _windowStyle = new GUIStyle(GUI.skin.window); _windowStyle.normal.background = _windowBackground; _windowStyle.onNormal.background = _windowBackground; _windowStyle.normal.textColor = new Color(0.78f, 1f, 0.88f, 1f); _windowStyle.fontStyle = (FontStyle)1; _windowStyle.border = new RectOffset(0, 0, 0, 0); _windowStyle.padding = new RectOffset(16, 16, 30, 14); _panelStyle = new GUIStyle(GUI.skin.box); _panelStyle.normal.background = _panelBackground; _panelStyle.normal.textColor = Color.white; _panelStyle.border = new RectOffset(0, 0, 0, 0); _panelStyle.padding = new RectOffset(12, 12, 10, 10); _panelStyle.margin = new RectOffset(2, 2, 4, 4); _sectionHeaderStyle = new GUIStyle(GUI.skin.box); _sectionHeaderStyle.normal.background = _panelBackground; _sectionHeaderStyle.normal.textColor = new Color(0.72f, 1f, 0.84f, 1f); _sectionHeaderStyle.border = new RectOffset(0, 0, 0, 0); _sectionHeaderStyle.fontStyle = (FontStyle)1; _sectionHeaderStyle.alignment = (TextAnchor)4; _sectionHeaderStyle.padding = new RectOffset(8, 8, 7, 7); } } private static Texture2D MakeSolidTexture(string textureName, Color32 color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_002a: 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_003d: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false) { name = textureName, hideFlags = (HideFlags)61, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)0 }; val.SetPixel(0, 0, Color32.op_Implicit(color)); val.Apply(false, true); return val; } private void CenterWindow() { //IL_0057: 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) float num = Mathf.Clamp((float)Screen.width - 80f, 480f, 720f); float num2 = Mathf.Clamp((float)Screen.height - 80f, 520f, 720f); _windowRect = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2); } private void DrawWindow(int windowId) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if (GUI.Button(new Rect(((Rect)(ref _windowRect)).width - 34f, 4f, 28f, 24f), "X")) { SetWindowOpen(open: false); } GUILayout.Space(12f); DrawTabs(); GUILayout.Space(8f); _scroll = GUILayout.BeginScrollView(_scroll, Array.Empty<GUILayoutOption>()); if (_selectedTab == MenuTab.DeveloperTools) { DrawDeveloperTools(); } else { DrawTwitchConnection(); GUILayout.Space(16f); DrawFeatures(); } GUILayout.EndScrollView(); GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width - 42f, 30f)); } private void DrawTabs() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = ((_selectedTab == MenuTab.Integration) ? new Color(0.36f, 0.82f, 0.6f, 1f) : new Color(0.22f, 0.42f, 0.36f, 1f)); if (GUILayout.Button("INTEGRATION", Array.Empty<GUILayoutOption>())) { SelectTab(MenuTab.Integration); } GUI.backgroundColor = ((_selectedTab == MenuTab.DeveloperTools) ? new Color(0.86f, 0.62f, 0.22f, 1f) : new Color(0.42f, 0.34f, 0.2f, 1f)); if (GUILayout.Button("DEVELOPER TOOLS", Array.Empty<GUILayoutOption>())) { SelectTab(MenuTab.DeveloperTools); } GUI.backgroundColor = backgroundColor; GUILayout.EndHorizontal(); } private void SelectTab(MenuTab tab) { //IL_0012: 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) if (_selectedTab != tab) { _selectedTab = tab; _scroll = Vector2.zero; } } private void DrawTwitchConnection() { TwitchConnectionSnapshot snapshot = Plugin.Twitch.Snapshot; GUILayout.Label("TWITCH CONNECTION", _sectionHeaderStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label(snapshot.Status, Array.Empty<GUILayoutOption>()); if (!string.IsNullOrEmpty(snapshot.AccountName)) { GUILayout.Label("Account: " + snapshot.AccountName, Array.Empty<GUILayoutOption>()); } GUILayout.Space(8f); GUILayout.Label("Public Twitch Client ID:", Array.Empty<GUILayoutOption>()); _clientIdDraft = GUILayout.TextField(_clientIdDraft ?? string.Empty, 128, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); bool enabled = GUI.enabled; GUI.enabled = !snapshot.IsBusy && !snapshot.IsConnected; if (GUILayout.Button("CONNECT", Array.Empty<GUILayoutOption>())) { Plugin.SetTwitchClientId(_clientIdDraft); Plugin.Twitch.Connect(); } GUI.enabled = !snapshot.IsBusy; if (GUILayout.Button("NEW AUTHORIZATION", Array.Empty<GUILayoutOption>())) { Plugin.SetTwitchClientId(_clientIdDraft); Plugin.Twitch.Connect(forceNewAuthorization: true); } GUI.enabled = snapshot.IsBusy || snapshot.IsConnected; if (GUILayout.Button("DISCONNECT", Array.Empty<GUILayoutOption>())) { Plugin.Twitch.Disconnect(forgetAuthorization: false); } GUI.enabled = enabled; GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(snapshot.UserCode)) { GUILayout.Space(10f); GUILayout.Label("Activation code: " + snapshot.UserCode, _panelStyle, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("OPEN TWITCH ACTIVATION", Array.Empty<GUILayoutOption>())) { Application.OpenURL(snapshot.VerificationUri); } if (GUILayout.Button("COPY CODE", Array.Empty<GUILayoutOption>())) { GUIUtility.systemCopyBuffer = snapshot.UserCode; } GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("CREATE / FIND CLIENT ID", Array.Empty<GUILayoutOption>())) { Application.OpenURL("https://dev.twitch.tv/console/apps"); } if (GUILayout.Button("FORGET SAVED ACCOUNT", Array.Empty<GUILayoutOption>())) { Plugin.Twitch.Disconnect(forgetAuthorization: true); } GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label("The Client ID is public. OAuth tokens are encrypted for your Windows account. The integration listens directly to Twitch EventSub while PEAK is running.", Array.Empty<GUILayoutOption>()); GUILayout.Label("Last redemption: " + snapshot.LastRedemption, Array.Empty<GUILayoutOption>()); } private void DrawFeatures() { //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_03e6: Unknown result type (might be due to invalid IL or missing references) GUILayout.Label("FEATURES", _sectionHeaderStyle, Array.Empty<GUILayoutOption>()); GUILayout.BeginVertical(_panelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("PEAK VOICE BALANCE", Array.Empty<GUILayoutOption>()); GUILayout.Label("Noise suppression, per-player automatic loudness leveling and a shared output level.", Array.Empty<GUILayoutOption>()); bool flag = GUILayout.Toggle(Plugin.ProcessingEnabled, Plugin.ProcessingEnabled ? "ENABLED" : "DISABLED", Array.Empty<GUILayoutOption>()); if (flag != Plugin.ProcessingEnabled) { Plugin.SetProcessingEnabled(flag); } GUILayout.Label($"Shared output: {Mathf.RoundToInt(Plugin.EqualizedVolumePercent)}%", Array.Empty<GUILayoutOption>()); float num = GUILayout.HorizontalSlider(Plugin.EqualizedVolumePercent, 0f, 200f, Array.Empty<GUILayoutOption>()); if (Mathf.Abs(num - Plugin.EqualizedVolumePercent) >= 0.5f) { Plugin.SetEqualizedVolumePercent(Mathf.Round(num)); } GUILayout.EndVertical(); GUILayout.Space(8f); GUILayout.BeginVertical(_panelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("CANNON LAUNCH", Array.Empty<GUILayoutOption>()); GUILayout.Label("Automatically manages the '[PEAK] Cannon Launch' Channel Points reward. The viewer must enter an exact PEAK player name.", Array.Empty<GUILayoutOption>()); bool flag2 = GUILayout.Toggle(Plugin.CannonLaunchEnabled, Plugin.CannonLaunchEnabled ? "ENABLED" : "DISABLED", Array.Empty<GUILayoutOption>()); if (flag2 != Plugin.CannonLaunchEnabled) { Plugin.SetCannonLaunchEnabled(flag2); } GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Reward cost:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }); _cannonCostDraft = GUILayout.TextField(_cannonCostDraft ?? string.Empty, 9, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); if (GUILayout.Button("APPLY", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { if (int.TryParse(_cannonCostDraft, out var result) && result > 0) { Plugin.SetCannonLaunchCost(result); _cannonCostDraft = Plugin.CannonLaunchCost.ToString(); } else { _cannonCostDraft = Plugin.CannonLaunchCost.ToString(); } } if (GUILayout.Button("SYNC REWARD", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) })) { Plugin.Twitch.SynchronizeCannonReward(); } GUILayout.EndHorizontal(); GUILayout.Label($"Launch power: {Mathf.RoundToInt(Plugin.CannonLaunchForce)}", Array.Empty<GUILayoutOption>()); float num2 = Mathf.Round(GUILayout.HorizontalSlider(Plugin.CannonLaunchForce, 250f, 5000f, Array.Empty<GUILayoutOption>()) / 50f) * 50f; if (Mathf.Abs(num2 - Plugin.CannonLaunchForce) >= 25f) { Plugin.SetCannonLaunchForce(num2); } GUILayout.Label(Plugin.Twitch.Snapshot.CannonRewardStatus, Array.Empty<GUILayoutOption>()); GUILayout.Label("Unknown, missing, ambiguous, dead or unavailable players are rejected and the viewer's points are refunded.", Array.Empty<GUILayoutOption>()); GUILayout.Label("The launch reaches vanilla clients. Guaranteed no-fall-damage for a remote player requires this mod on that player's client too.", Array.Empty<GUILayoutOption>()); GUILayout.EndVertical(); GUILayout.Space(8f); GUILayout.BeginVertical(_panelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("TEAMMATE PUSH", Array.Empty<GUILayoutOption>()); GUILayout.Label($"Look directly at a nearby teammate and press {Plugin.TeammatePushKey} to shove them backward and knock them over.", Array.Empty<GUILayoutOption>()); bool flag3 = GUILayout.Toggle(Plugin.TeammatePushEnabled, Plugin.TeammatePushEnabled ? "ENABLED" : "DISABLED", Array.Empty<GUILayoutOption>()); if (flag3 != Plugin.TeammatePushEnabled) { Plugin.SetTeammatePushEnabled(flag3); } GUILayout.Label($"Shared push force: {Mathf.RoundToInt(Plugin.EffectiveTeammatePushForce)}", Array.Empty<GUILayoutOption>()); bool enabled = GUI.enabled; GUI.enabled = enabled && Plugin.CanEditTeammatePushForce; float num3 = Mathf.Round(GUILayout.HorizontalSlider(Plugin.EffectiveTeammatePushForce, 200f, 800f, Array.Empty<GUILayoutOption>()) / 25f) * 25f; if (Plugin.CanEditTeammatePushForce && Mathf.Abs(num3 - Plugin.EffectiveTeammatePushForce) >= 12.5f) { Plugin.SetTeammatePushForce(num3); } GUI.enabled = enabled; GUILayout.Label(Plugin.TeammatePushForceControlStatus, Array.Empty<GUILayoutOption>()); GUILayout.Label($"Key: {Plugin.TeammatePushKey} | Range: {Plugin.TeammatePushRange:0.##} m | Fall protection: {Plugin.TeammatePushFallProtectionSeconds:0.#} s", Array.Empty<GUILayoutOption>()); GUILayout.Label("Fall protection ends at the first genuine landing. The pushed player needs this mod for that protection. No Twitch chat permission is used.", Array.Empty<GUILayoutOption>()); GUILayout.EndVertical(); } private void DrawDeveloperTools() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) GUILayout.Label("DEVELOPER TOOLS", _sectionHeaderStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("Local test utilities for development. The push dummy is not a real Photon user and does not occupy a multiplayer slot.", Array.Empty<GUILayoutOption>()); GUILayout.Space(8f); GUILayout.BeginVertical(_panelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("PUSH TEST BOT", Array.Empty<GUILayoutOption>()); GUILayout.Label("Spawns one stationary PEAK Character/Ragdoll dummy one metre in front of you. A second PEAK-style bar shows its stamina, debuffs, and received damage.", Array.Empty<GUILayoutOption>()); bool canU