using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; 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 HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("GameAssembly")] [assembly: IgnoresAccessChecksTo("SharedAssembly")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("com.github.addzeey.SbgShock")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("com.github.addzeey.SbgShock")] [assembly: AssemblyTitle("Super Battle Shock")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.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 SbgShock { internal interface IShockController : IDisposable { void EnqueueShock(int intensity, int duration, string? code = null); } internal static class NotificationOverlay { private readonly struct OverlayEntry { internal SbgNotification Notification { get; } internal float ExpiresAt { get; } internal OverlayEntry(SbgNotification notification, float expiresAt) { Notification = notification; ExpiresAt = expiresAt; } } private const int MaximumVisibleNotifications = 5; private const float CardWidth = 400f; private const float CardHeight = 48f; private const float CardGap = 7f; private const float ScreenMargin = 24f; private const float FadeDuration = 0.35f; private static readonly List Entries = new List(); private static GUIStyle? _cardStyle; private static GUIStyle? _messageStyle; private static Texture2D? _cardTexture; private static Texture2D? _lightningTexture; internal static void Enqueue(SbgNotification notification) { float num = Mathf.Clamp(Plugin.NotificationDurationSeconds.Value, 1f, 15f); Entries.Add(new OverlayEntry(notification, Time.realtimeSinceStartup + num)); if (Entries.Count > 5) { Entries.RemoveAt(0); } } internal static void Draw() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: 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_015f: 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) if (Plugin.NotificationDisplay == null || Plugin.NotificationDisplay.Value != Plugin.NotificationDisplayMode.Overlay || Entries.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; for (int num = Entries.Count - 1; num >= 0; num--) { if (Entries[num].ExpiresAt <= realtimeSinceStartup) { Entries.RemoveAt(num); } } if (Entries.Count != 0 && (int)Event.current.type == 7) { EnsureResources(); float num2 = Mathf.Min(400f, (float)Screen.width - 48f); float num3 = (float)Entries.Count * 48f + (float)(Entries.Count - 1) * 7f; float num4 = (float)Screen.width - num2 - 24f; float num5 = Mathf.Max(24f, ((float)Screen.height - num3) * 0.5f); Color color = GUI.color; Color contentColor = GUI.contentColor; for (int num6 = Entries.Count - 1; num6 >= 0; num6--) { OverlayEntry overlayEntry = Entries[num6]; float alpha = Mathf.Clamp01((overlayEntry.ExpiresAt - realtimeSinceStartup) / 0.35f); DrawCard(new Rect(num4, num5, num2, 48f), overlayEntry.Notification, alpha); num5 += 55f; } GUI.color = color; GUI.contentColor = contentColor; } } internal static void Dispose() { Entries.Clear(); DestroyTexture(ref _cardTexture); DestroyTexture(ref _lightningTexture); _cardStyle = null; _messageStyle = null; } private static void DrawCard(Rect rect, SbgNotification notification, float alpha) { //IL_0016: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: 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_0133: Unknown result type (might be due to invalid IL or missing references) GUI.color = new Color(0f, 0f, 0f, 0.28f * alpha); GUI.Box(new Rect(((Rect)(ref rect)).x + 2f, ((Rect)(ref rect)).y + 3f, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height), GUIContent.none, _cardStyle); GUI.color = new Color(1f, 1f, 1f, alpha); GUI.Box(rect, GUIContent.none, _cardStyle); Color accentColor = GetAccentColor(notification.Kind); GUI.color = new Color(accentColor.r, accentColor.g, accentColor.b, alpha); GUI.DrawTexture(new Rect(((Rect)(ref rect)).x + 17f, ((Rect)(ref rect)).y + 12f, 17f, 24f), (Texture)(object)_lightningTexture, (ScaleMode)2, true); GUI.contentColor = new Color(1f, 1f, 1f, alpha); GUI.Label(new Rect(((Rect)(ref rect)).x + 47f, ((Rect)(ref rect)).y + 6f, ((Rect)(ref rect)).width - 63f, ((Rect)(ref rect)).height - 12f), notification.Message, _messageStyle); } private static void EnsureResources() { //IL_0035: 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_004a: 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_005d: Expected O, but got Unknown //IL_0062: Expected O, but got Unknown //IL_0073: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_cardTexture == (Object)null) { _cardTexture = CreateRoundedCardTexture(); } if ((Object)(object)_lightningTexture == (Object)null) { _lightningTexture = CreateLightningTexture(); } if (_cardStyle == null) { GUIStyle val = new GUIStyle(); val.normal.background = _cardTexture; val.border = new RectOffset(15, 15, 15, 15); _cardStyle = val; } if (_messageStyle == null) { _messageStyle = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)3, clipping = (TextClipping)1, fontSize = 15, fontStyle = (FontStyle)1, padding = new RectOffset(0, 0, 0, 0), richText = false, wordWrap = false }; _messageStyle.normal.textColor = Color.white; } } private static Texture2D CreateRoundedCardTexture() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_00bc: Unknown result type (might be due to invalid IL or missing references) Color val = default(Color); ((Color)(ref val))..ctor(0.22f, 0.2f, 0.23f, 0.9f); Color val2 = default(Color); ((Color)(ref val2))..ctor(0.11f, 0.1f, 0.12f, 0.86f); Color[] array = (Color[])(object)new Color[2048]; Vector2 point = default(Vector2); for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { ((Vector2)(ref point))..ctor((float)j + 0.5f, (float)i + 0.5f); if (IsInsideRoundedRect(point, 0f, 0f, 64f, 32f, 14f)) { bool flag = IsInsideRoundedRect(point, 2f, 2f, 60f, 28f, 12f); array[i * 64 + j] = (flag ? val2 : val); } } } Texture2D val3 = CreateTexture(64, 32, (FilterMode)1); val3.SetPixels(array); val3.Apply(false, true); return val3; } private static Texture2D CreateLightningTexture() { //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) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_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_0054: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) Vector2[] polygon = (Vector2[])(object)new Vector2[7] { new Vector2(10f, 27f), new Vector2(18f, 27f), new Vector2(13f, 17f), new Vector2(19f, 17f), new Vector2(5f, 0f), new Vector2(9f, 12f), new Vector2(2f, 12f) }; Color[] array = (Color[])(object)new Color[560]; for (int i = 0; i < 28; i++) { for (int j = 0; j < 20; j++) { if (IsPointInPolygon(new Vector2((float)j + 0.5f, (float)i + 0.5f), polygon)) { array[i * 20 + j] = Color.white; } } } Texture2D val = CreateTexture(20, 28, (FilterMode)0); val.SetPixels(array); val.Apply(false, true); return val; } private static Texture2D CreateTexture(int width, int height, FilterMode filterMode) { //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_000a: 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_0020: Expected O, but got Unknown return new Texture2D(width, height, (TextureFormat)4, false) { filterMode = filterMode, hideFlags = (HideFlags)61, wrapMode = (TextureWrapMode)1 }; } private static bool IsInsideRoundedRect(Vector2 point, float x, float y, float width, float height, float radius) { //IL_0000: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Clamp(point.x, x + radius, x + width - radius); float num2 = Mathf.Clamp(point.y, y + radius, y + height - radius); float num3 = point.x - num; float num4 = point.y - num2; return num3 * num3 + num4 * num4 <= radius * radius; } private static bool IsPointInPolygon(Vector2 point, IReadOnlyList polygon) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) bool flag = false; int num = 0; int index = polygon.Count - 1; while (num < polygon.Count) { Vector2 val = polygon[num]; Vector2 val2 = polygon[index]; if (val.y > point.y != val2.y > point.y && point.x < (val2.x - val.x) * (point.y - val.y) / (val2.y - val.y) + val.x) { flag = !flag; } index = num++; } return flag; } private static Color GetAccentColor(NotificationKind kind) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) return (Color)(kind switch { NotificationKind.Warning => new Color(1f, 0.72f, 0.25f), NotificationKind.Error => new Color(1f, 0.55f, 0.62f), _ => new Color(0.82f, 0.9f, 0.25f), }); } private static void DestroyTexture(ref Texture2D? texture) { if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } texture = null; } } internal enum NotificationKind { Info, Warning, Error } internal readonly struct SbgNotification { internal string Message { get; } internal NotificationKind Kind { get; } internal SbgNotification(string message, NotificationKind kind) { Message = message; Kind = kind; } } internal static class NotificationService { private const int MaximumPendingMessages = 20; private static readonly ConcurrentQueue PendingMessages = new ConcurrentQueue(); private static readonly object QueueGate = new object(); private static int _pendingCount; internal static void Publish(string message, bool showInGame, string? displayMessage = null) { Publish(message, NotificationKind.Info, showInGame, displayMessage); } internal static void PublishWarning(string message, bool showInGame, string? displayMessage = null) { Publish(message, NotificationKind.Warning, showInGame, displayMessage); } internal static void PublishError(string message, string displayMessage = "Shock failed. Check LogOutput.log.") { Publish(message, NotificationKind.Error, showInGame: true, displayMessage); } internal static void FlushToGame() { if (Plugin.NotificationDisplay == null || Plugin.NotificationDisplay.Value == Plugin.NotificationDisplayMode.Disabled) { ClearPendingMessages(); } else if (Plugin.NotificationDisplay.Value == Plugin.NotificationDisplayMode.ChatFeed) { if (SingletonBehaviour.HasInstance) { SbgNotification notification; while (TryDequeue(out notification)) { TextChatUi.ShowMessage("[Super Battle Shock] " + GameManager.RichTextNoParse(notification.Message)); } } } else { SbgNotification notification2; while (TryDequeue(out notification2)) { NotificationOverlay.Enqueue(notification2); } } } internal static void Clear() { ClearPendingMessages(); } private static void Publish(string message, NotificationKind kind, bool showInGame, string? displayMessage) { string text = "[Super Battle Shock] " + message; switch (kind) { case NotificationKind.Warning: Plugin.Log.LogWarning((object)text); break; case NotificationKind.Error: Plugin.Log.LogError((object)text); break; default: Plugin.Log.LogInfo((object)text); break; } if (!showInGame || Plugin.NotificationDisplay == null || Plugin.NotificationDisplay.Value == Plugin.NotificationDisplayMode.Disabled) { return; } lock (QueueGate) { PendingMessages.Enqueue(new SbgNotification(displayMessage ?? message, kind)); _pendingCount++; if (_pendingCount > 20 && PendingMessages.TryDequeue(out var _)) { _pendingCount--; } } } private static void ClearPendingMessages() { SbgNotification notification; while (TryDequeue(out notification)) { } } private static bool TryDequeue(out SbgNotification notification) { lock (QueueGate) { if (!PendingMessages.TryDequeue(out notification)) { return false; } _pendingCount--; return true; } } } internal sealed class OpenShockController : ShockControllerBase { public override void EnqueueShock(int intensity, int duration, string? code = null) { intensity = ShockControllerBase.ClampIntensity(intensity); duration = ShockControllerBase.ClampDuration(duration); string apiUrl = Plugin.OpenShockApiUrl.Value.TrimEnd('/'); string apiKey = Plugin.OpenShockApiKey.Value; string deviceId = (string.IsNullOrWhiteSpace(code) ? Plugin.OpenShockDeviceId.Value : code); if (string.IsNullOrWhiteSpace(apiUrl) || string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(deviceId)) { NotificationService.PublishWarning($"Dry run: would send OpenShock intensity={intensity}, duration={duration}s. Configure ApiUrl, ApiKey, and DeviceId.", showInGame: true, "Dry run: " + ShockControllerBase.FormatShockSummary(intensity, duration)); return; } EnqueueRequest("OpenShock", intensity, duration, (CancellationToken cancellationToken) => SendShockAsync(apiUrl, apiKey, deviceId, intensity, duration, cancellationToken)); } private async Task SendShockAsync(string apiUrl, string apiKey, string deviceId, int intensity, int duration, CancellationToken cancellationToken) { int duration2 = duration * 1000; var anon = new { Shocks = new[] { new { Id = deviceId, Type = 1, Intensity = intensity, Duration = duration2 } }, CustomName = "Integrations.SuperBattleShock" }; try { using StringContent content = new StringContent(JsonConvert.SerializeObject((object)anon), Encoding.UTF8, "application/json"); using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, apiUrl + "/2/shockers/control") { Content = content }; request.Headers.Add("OpenShockToken", apiKey); request.Headers.Add("User-Agent", "SuperBattleShock/1.0.0"); using HttpResponseMessage response = await base.Client.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken); if (response.IsSuccessStatusCode) { NotificationService.Publish($"OpenShock accepted intensity={intensity}, duration={duration}s.", showInGame: true, ShockControllerBase.FormatShockSummary(intensity, duration) + " sent."); return; } string responseBody = await response.Content.ReadAsStringAsync(); NotificationService.PublishError($"OpenShock rejected shock: {(int)response.StatusCode} {response.StatusCode}. " + "Response: " + ShockControllerBase.FormatResponseBody(responseBody)); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { } catch (Exception ex2) { NotificationService.PublishError("OpenShock request failed: " + ex2.Message); Plugin.Log.LogError((object)ex2); } } } internal sealed class PiShockController : ShockControllerBase { private const string PiShockIdLookupUrl = "https://pishock-lookup.addzeey.com/"; private const string PiShockV3DocsUrl = "https://docs.pishock.com/pishock/pishock-v3-documentation.html"; public override void EnqueueShock(int intensity, int duration, string? code = null) { intensity = ShockControllerBase.ClampIntensity(intensity); duration = ShockControllerBase.ClampDuration(duration); string apiUrl = Plugin.PiShockApiUrl.Value.TrimEnd('/'); string apiKey = Plugin.PiShockApiKey.Value; string shockerId = (string.IsNullOrWhiteSpace(code) ? Plugin.PiShockShockerId.Value : code); if (string.IsNullOrWhiteSpace(apiUrl) || string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(shockerId)) { NotificationService.PublishWarning(string.Format("Dry run: would send PiShock intensity={0}, duration={1}s. Configure ApiKey and ShockerId; lookup: {2}", intensity, duration, "https://pishock-lookup.addzeey.com/"), showInGame: true, "Dry run: " + ShockControllerBase.FormatShockSummary(intensity, duration)); return; } EnqueueRequest("PiShock", intensity, duration, (CancellationToken cancellationToken) => SendShockAsync(apiUrl, apiKey, shockerId, intensity, duration, cancellationToken)); } private async Task SendShockAsync(string apiUrl, string apiKey, string shockerId, int intensity, int duration, CancellationToken cancellationToken) { string content = JsonConvert.SerializeObject((object)new { AgentName = "Super Battle Shock Mod", Intensity = intensity, Duration = duration * 1000, Operation = 0 }); try { using StringContent content2 = new StringContent(content, Encoding.UTF8, "application/json"); using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, apiUrl + "/Shockers/" + Uri.EscapeDataString(shockerId)) { Content = content2 }; request.Headers.Add("X-PiShock-Api-Key", apiKey); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); using HttpResponseMessage response = await base.Client.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken); if (response.IsSuccessStatusCode) { NotificationService.Publish($"PiShock accepted intensity={intensity}, duration={duration}s.", showInGame: true, ShockControllerBase.FormatShockSummary(intensity, duration) + " sent."); return; } string responseBody = await response.Content.ReadAsStringAsync(); NotificationService.PublishError($"PiShock rejected shock: {(int)response.StatusCode} {response.StatusCode}. " + GetStatusExplanation(response.StatusCode) + " Response: " + ShockControllerBase.FormatResponseBody(responseBody)); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { } catch (Exception ex2) { NotificationService.PublishError("PiShock request failed: " + ex2.Message); Plugin.Log.LogError((object)ex2); } } private static string GetStatusExplanation(HttpStatusCode statusCode) { return statusCode switch { HttpStatusCode.Unauthorized => "Verify the API key.", HttpStatusCode.Forbidden => "Account access was rejected.", HttpStatusCode.NotFound => "Verify the shocker ID with https://pishock-lookup.addzeey.com/", HttpStatusCode.MethodNotAllowed => "This share does not permit shocks.", HttpStatusCode.NotAcceptable => "Device must use PiShock V3 firmware: https://docs.pishock.com/pishock/pishock-v3-documentation.html", HttpStatusCode.Gone => "The share is locked.", HttpStatusCode.PreconditionFailed => "Intensity is outside share limits.", HttpStatusCode.RequestedRangeNotSatisfiable => "Duration exceeds PiShock limits.", HttpStatusCode.ServiceUnavailable => "The share or shocker is paused.", _ => "Check PiShock status and configuration.", }; } } [BepInPlugin("com.github.addzeey.SbgShock", "Super Battle Shock", "1.0.0")] public class Plugin : BaseUnityPlugin { public enum ShockProvider { PiShock, OpenShock } public enum HitTriggerMode { Disabled, AnyHit, KnockoutOnly } public enum NotificationDisplayMode { Disabled, Overlay, ChatFeed } public const string PluginGuid = "com.github.addzeey.SbgShock"; public const string PluginName = "Super Battle Shock"; public const string PluginVersion = "1.0.0"; private static readonly object ShockControllerGate = new object(); private static IShockController? _shockController; private Harmony? _harmony; internal static ManualLogSource Log { get; private set; } = null; internal static ConfigFile CFG { get; private set; } = null; internal static ConfigEntry ShockProviderType { get; private set; } = null; internal static ConfigEntry ShockCooldownSeconds { get; private set; } = null; internal static ConfigEntry EnableShocksInLobby { get; private set; } = null; internal static ConfigEntry PiShockApiUrl { get; private set; } = null; internal static ConfigEntry PiShockApiKey { get; private set; } = null; internal static ConfigEntry PiShockShockerId { get; private set; } = null; internal static ConfigEntry OpenShockApiUrl { get; private set; } = null; internal static ConfigEntry OpenShockDeviceId { get; private set; } = null; internal static ConfigEntry OpenShockApiKey { get; private set; } = null; internal static ConfigEntry EnableFailedHoleShock { get; private set; } = null; internal static ConfigEntry FailedHoleIntensity { get; private set; } = null; internal static ConfigEntry FailedHoleDuration { get; private set; } = null; internal static ConfigEntry EnableWaterDeathShock { get; private set; } = null; internal static ConfigEntry WaterDeathIntensity { get; private set; } = null; internal static ConfigEntry WaterDeathDuration { get; private set; } = null; internal static ConfigEntry EnableFogDeathShock { get; private set; } = null; internal static ConfigEntry FogDeathIntensity { get; private set; } = null; internal static ConfigEntry FogDeathDuration { get; private set; } = null; internal static ConfigEntry ClubHitMode { get; private set; } = null; internal static ConfigEntry ClubHitIntensity { get; private set; } = null; internal static ConfigEntry ClubHitDuration { get; private set; } = null; internal static ConfigEntry GolfBallHitMode { get; private set; } = null; internal static ConfigEntry GolfBallHitIntensity { get; private set; } = null; internal static ConfigEntry GolfBallHitDuration { get; private set; } = null; internal static ConfigEntry DuelingPistolHitMode { get; private set; } = null; internal static ConfigEntry ElephantGunHitMode { get; private set; } = null; internal static ConfigEntry RailgunHitMode { get; private set; } = null; internal static ConfigEntry WeaponHitIntensity { get; private set; } = null; internal static ConfigEntry WeaponHitDuration { get; private set; } = null; internal static ConfigEntry RocketHitMode { get; private set; } = null; internal static ConfigEntry RocketBackBlastHitMode { get; private set; } = null; internal static ConfigEntry LandmineHitMode { get; private set; } = null; internal static ConfigEntry OrbitalLaserHitMode { get; private set; } = null; internal static ConfigEntry ThunderstormHitMode { get; private set; } = null; internal static ConfigEntry ElectromagnetExplosionHitMode { get; private set; } = null; internal static ConfigEntry ExplosiveHitIntensity { get; private set; } = null; internal static ConfigEntry ExplosiveHitDuration { get; private set; } = null; internal static ConfigEntry GolfCartHitMode { get; private set; } = null; internal static ConfigEntry GolfCartIntensity { get; private set; } = null; internal static ConfigEntry GolfCartDuration { get; private set; } = null; internal static ConfigEntry RocketDriverHitMode { get; private set; } = null; internal static ConfigEntry JumboBurgerHitMode { get; private set; } = null; internal static ConfigEntry TrafficVehicleHitMode { get; private set; } = null; internal static ConfigEntry EnableUnmappedKnockoutShock { get; private set; } = null; internal static ConfigEntry UnmappedKnockoutIntensity { get; private set; } = null; internal static ConfigEntry UnmappedKnockoutDuration { get; private set; } = null; internal static ConfigEntry NotificationDisplay { get; private set; } = null; internal static ConfigEntry ShowSkippedNotifications { get; private set; } = null; internal static ConfigEntry NotificationDurationSeconds { get; private set; } = null; private void Awake() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; CFG = ((BaseUnityPlugin)this).Config; BindConfig(); InitializeShockController(); ShockProviderType.SettingChanged += OnShockProviderChanged; _harmony = new Harmony("com.github.addzeey.SbgShock"); try { _harmony.PatchAll(Assembly.GetExecutingAssembly()); } catch (Exception arg) { _harmony.UnpatchSelf(); Log.LogError((object)$"[Super Battle Shock] Failed to apply gameplay hooks: {arg}"); NotificationService.Publish("Hook setup failed; check LogOutput.log.", showInGame: true); return; } Log.LogInfo((object)"Plugin Super Battle Shock is loaded!"); NotificationService.Publish("Loaded v1.0.0. Event logging is active.", showInGame: true); } private void Update() { NotificationService.FlushToGame(); } private void OnGUI() { NotificationOverlay.Draw(); } private void OnDestroy() { ShockProviderType.SettingChanged -= OnShockProviderChanged; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } DisposeShockController(); ShockControllerBase.ResetCooldown(); EliminationShockHandler.ClearPending(); NotificationService.Clear(); NotificationOverlay.Dispose(); ShockDispatcher.Reset(); } private static void BindConfig() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_0546: Expected O, but got Unknown ShockProviderType = CFG.Bind("Shock", "Provider", ShockProvider.PiShock, "Choose PiShock or OpenShock."); ShockCooldownSeconds = CFG.Bind("Shock", "ShockCooldownSeconds", 2f, new ConfigDescription("Minimum seconds between outgoing requests.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); EnableShocksInLobby = CFG.Bind("Shock", "EnableShocksInLobby", false, "Allow shocks in the multiplayer Driving Range lobby. Disabled by default."); PiShockApiUrl = CFG.Bind("PiShock", "ApiUrl", "https://api.pishock.com", "PiShock API URL."); PiShockApiKey = CFG.Bind("PiShock", "ApiKey", string.Empty, "Your PiShock API key."); PiShockShockerId = CFG.Bind("PiShock", "ShockerId", string.Empty, "Your PiShock V3 shocker ID."); OpenShockApiUrl = CFG.Bind("OpenShock", "ApiUrl", "https://api.openshock.app", "OpenShock API URL."); OpenShockDeviceId = CFG.Bind("OpenShock", "DeviceId", string.Empty, "OpenShock device ID or share ID."); OpenShockApiKey = CFG.Bind("OpenShock", "ApiKey", string.Empty, "OpenShock API key."); EnableFailedHoleShock = CFG.Bind("Events", "EnableFailedHoleShock", true, "Shock when the local player times out instead of finishing the hole."); FailedHoleIntensity = BindIntensity("Events", "FailedHoleIntensity", 55, "Intensity for a failed hole timeout shock."); FailedHoleDuration = BindDuration("Events", "FailedHoleDuration", 2, "Duration for a failed hole timeout shock."); EnableWaterDeathShock = CFG.Bind("Deaths", "EnableWaterDeathShock", true, "Shock when the local player falls into water and starts waiting to respawn."); WaterDeathIntensity = BindIntensity("Deaths", "WaterDeathIntensity", 10, "Intensity for falling into water."); WaterDeathDuration = BindDuration("Deaths", "WaterDeathDuration", 1, "Duration for falling into water."); EnableFogDeathShock = CFG.Bind("Deaths", "EnableFogDeathShock", true, "Shock when the local player falls into fog and starts waiting to respawn."); FogDeathIntensity = BindIntensity("Deaths", "FogDeathIntensity", 10, "Intensity for falling into fog."); FogDeathDuration = BindDuration("Deaths", "FogDeathDuration", 1, "Duration for falling into fog."); ClubHitMode = BindHitMode("ClubHitMode", "club swings"); ClubHitIntensity = BindIntensity("Events", "ClubHitIntensity", 25, "Intensity for club hit shocks."); ClubHitDuration = BindDuration("Events", "ClubHitDuration", 1, "Duration for club hit shocks."); GolfBallHitMode = BindHitMode("GolfBallHitMode", "club-launched or reflected golf balls"); GolfBallHitIntensity = BindIntensity("Events", "GolfBallHitIntensity", 25, "Intensity for golf ball hit shocks."); GolfBallHitDuration = BindDuration("Events", "GolfBallHitDuration", 1, "Duration for golf ball hit shocks."); DuelingPistolHitMode = BindHitMode("DuelingPistolHitMode", "dueling pistol hits"); ElephantGunHitMode = BindHitMode("ElephantGunHitMode", "elephant gun hits"); RailgunHitMode = BindHitMode("RailgunHitMode", "railgun hits"); WeaponHitIntensity = BindIntensity("Events", "WeaponHitIntensity", 35, "Intensity for direct weapon hit shocks."); WeaponHitDuration = BindDuration("Events", "WeaponHitDuration", 1, "Duration for direct weapon hit shocks."); RocketHitMode = BindHitMode("RocketHitMode", "rocket hits"); RocketBackBlastHitMode = BindHitMode("RocketBackBlastHitMode", "rocket backblast hits"); LandmineHitMode = BindHitMode("LandmineHitMode", "landmine hits"); OrbitalLaserHitMode = BindHitMode("OrbitalLaserHitMode", "orbital laser hits"); ThunderstormHitMode = BindHitMode("ThunderstormHitMode", "thunderstorm hits"); ElectromagnetExplosionHitMode = BindHitMode("ElectromagnetExplosionHitMode", "electromagnet shield explosions"); ExplosiveHitIntensity = BindIntensity("Events", "ExplosiveHitIntensity", 50, "Intensity for explosive hit shocks."); ExplosiveHitDuration = BindDuration("Events", "ExplosiveHitDuration", 2, "Duration for explosive hit shocks."); GolfCartHitMode = BindHitMode("GolfCartHitMode", "golf cart hits"); GolfCartIntensity = BindIntensity("Events", "GolfCartIntensity", 45, "Intensity for golf cart shocks."); GolfCartDuration = BindDuration("Events", "GolfCartDuration", 2, "Duration for golf cart shocks."); RocketDriverHitMode = BindHitMode("RocketDriverHitMode", "Rocket Driver hits"); JumboBurgerHitMode = BindHitMode("JumboBurgerHitMode", "Jumbo Burger giant hits"); TrafficVehicleHitMode = BindHitMode("TrafficVehicleHitMode", "traffic vehicle hits"); EnableUnmappedKnockoutShock = CFG.Bind("Events", "EnableUnmappedKnockoutShock", false, "Optional shock for new knockout causes without a dedicated hit setting."); UnmappedKnockoutIntensity = BindIntensity("Events", "UnmappedKnockoutIntensity", 30, "Intensity for unmapped knockout shocks."); UnmappedKnockoutDuration = BindDuration("Events", "UnmappedKnockoutDuration", 1, "Duration for unmapped knockout shocks."); NotificationDisplay = CFG.Bind("Notifications", "DisplayMode", NotificationDisplayMode.Overlay, "Where local Super Battle Shock status messages appear: Disabled, Overlay, or ChatFeed."); ShowSkippedNotifications = CFG.Bind("Notifications", "ShowSkippedNotifications", false, "Show cooldown and disabled-event skips in game. All skips are still logged."); NotificationDurationSeconds = CFG.Bind("Notifications", "DurationSeconds", 4f, new ConfigDescription("How long overlay notifications remain visible.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); static ConfigEntry BindDuration(string section, string key, int defaultValue, string description) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown return CFG.Bind(section, key, defaultValue, new ConfigDescription(description + " Range: 1-15 seconds.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 15), Array.Empty())); } static ConfigEntry BindHitMode(string key, string cause) { return CFG.Bind("Events", key, HitTriggerMode.KnockoutOnly, "When " + cause + " trigger a shock: Disabled, AnyHit, or KnockoutOnly."); } static ConfigEntry BindIntensity(string section, string key, int defaultValue, string description) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown return CFG.Bind(section, key, defaultValue, new ConfigDescription(description + " Range: 1-100.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); } } private static void InitializeShockController() { IShockController shockController2; if (ShockProviderType.Value != ShockProvider.PiShock) { IShockController shockController = new OpenShockController(); shockController2 = shockController; } else { IShockController shockController = new PiShockController(); shockController2 = shockController; } IShockController shockController3 = shockController2; IShockController shockController4; lock (ShockControllerGate) { shockController4 = _shockController; _shockController = null; } shockController4?.Dispose(); lock (ShockControllerGate) { _shockController = shockController3; } Log.LogInfo((object)$"[Super Battle Shock] Initialized {ShockProviderType.Value} controller."); } internal static bool EnqueueShock(int intensity, int duration) { lock (ShockControllerGate) { if (_shockController == null) { return false; } _shockController.EnqueueShock(intensity, duration); return true; } } private static void DisposeShockController() { IShockController shockController; lock (ShockControllerGate) { shockController = _shockController; _shockController = null; } shockController?.Dispose(); } private static void OnShockProviderChanged(object sender, EventArgs args) { InitializeShockController(); NotificationService.Publish($"Provider changed to {ShockProviderType.Value}.", showInGame: true); } } internal abstract class ShockControllerBase : IShockController, IDisposable { private const int MaximumResponseBodyLength = 512; private const int MaximumResponseBufferLength = 4096; private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10.0); private static readonly object CooldownGate = new object(); private static double _lastRequestTime = double.NegativeInfinity; private readonly ShockRequestQueue _queue = new ShockRequestQueue(); protected HttpClient Client { get; } = new HttpClient(); protected ShockControllerBase() { Client.Timeout = RequestTimeout; Client.MaxResponseContentBufferSize = 4096L; } public abstract void EnqueueShock(int intensity, int duration, string? code = null); protected void EnqueueRequest(string providerName, int intensity, int duration, Func sendRequest) { if (_queue.TryEnqueue(async delegate(CancellationToken cancellationToken) { if (!TryReserveCooldown(out var remainingSeconds)) { NotificationService.Publish($"{providerName} request skipped by cooldown ({remainingSeconds:0.0}s remaining).", Plugin.ShowSkippedNotifications.Value, "Shock skipped: cooldown active."); } else { await sendRequest(cancellationToken); } })) { NotificationService.Publish($"{providerName} request queued: intensity={intensity}, duration={duration}s.", showInGame: false); } else { NotificationService.PublishWarning(providerName + " request skipped because another request is already in progress.", Plugin.ShowSkippedNotifications.Value, "Shock skipped: request already in progress."); } } protected static int ClampIntensity(int intensity) { return Math.Clamp(intensity, 1, 100); } protected static int ClampDuration(int duration) { return Math.Clamp(duration, 1, 15); } protected static string FormatShockSummary(int intensity, int duration) { return $"{duration} second shock at {intensity}%"; } protected static string FormatResponseBody(string responseBody) { if (string.IsNullOrWhiteSpace(responseBody)) { return ""; } int num = Math.Min(responseBody.Length, 512); StringBuilder stringBuilder = new StringBuilder(num + 3); for (int i = 0; i < num; i++) { char c = responseBody[i]; stringBuilder.Append((char.IsControl(c) && !char.IsWhiteSpace(c)) ? '?' : c); } if (responseBody.Length > num) { stringBuilder.Append("..."); } return stringBuilder.ToString(); } internal static void ResetCooldown() { lock (CooldownGate) { _lastRequestTime = double.NegativeInfinity; } } private bool TryReserveCooldown(out double remainingSeconds) { lock (CooldownGate) { double num = (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency; double num2 = Plugin.ShockCooldownSeconds.Value; double num3 = num - _lastRequestTime; if (num3 < num2) { remainingSeconds = num2 - num3; return false; } _lastRequestTime = num; remainingSeconds = 0.0; return true; } } public void Dispose() { _queue.Dispose(); Client.Dispose(); } } internal static class ShockDispatcher { private const double LocalDedupWindowSeconds = 0.75; private static readonly Dictionary RecentEvents = new Dictionary(); private static readonly Dictionary RecentDuplicateLogs = new Dictionary(); private static readonly object Gate = new object(); internal static void Trigger(string eventKey, int intensity, int duration, string reason) { if (!Plugin.EnableShocksInLobby.Value && SingletonBehaviour.HasInstance) { NotificationService.Publish("Ignored " + reason + ": shocks are disabled in the Driving Range lobby.", Plugin.ShowSkippedNotifications.Value, "Shock skipped: disabled in lobby."); return; } intensity = Math.Clamp(intensity, 1, 100); duration = Math.Clamp(duration, 1, 15); double num = (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency; bool flag = false; bool flag2; lock (Gate) { flag2 = RecentEvents.TryGetValue(eventKey, out var value) && num - value < 0.75; RecentEvents[eventKey] = num; if (flag2 && (!RecentDuplicateLogs.TryGetValue(eventKey, out var value2) || num - value2 >= 0.75)) { RecentDuplicateLogs[eventKey] = num; flag = true; } } if (flag2) { if (flag) { NotificationService.Publish($"Ignored repeated {reason} within {750.0:0}ms.", Plugin.ShowSkippedNotifications.Value, "Shock skipped: repeated event."); } } else { NotificationService.Publish($"Detected {reason}: intensity={intensity}, duration={duration}s.", showInGame: false); Plugin.EnqueueShock(intensity, duration); } } internal static void Reset() { lock (Gate) { RecentEvents.Clear(); RecentDuplicateLogs.Clear(); } } } [HarmonyPatch] internal static class ShockHooks { [HarmonyPatch(typeof(PlayerMovement), "TryKnockOut")] [HarmonyPostfix] private static void TryKnockOutPostfix(PlayerMovement __instance, KnockoutType knockoutType, bool __result, bool isNewKnockout) { //IL_0009: 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_0058: Unknown result type (might be due to invalid IL or missing references) if (LocalPlayerGuard.IsLocal(__instance)) { if (ShockHitHandler.TryGetSettings(knockoutType, out var settings)) { ShockHitHandler.Handle(settings, __result, isNewKnockout); } else if (__result && isNewKnockout && Plugin.EnableUnmappedKnockoutShock.Value) { ShockDispatcher.Trigger($"ragdoll-{knockoutType}", Plugin.UnmappedKnockoutIntensity.Value, Plugin.UnmappedKnockoutDuration.Value, $"unmapped knockout via {knockoutType}"); } } } [HarmonyPatch(typeof(PlayerMovement), "OnLocalPlayerWillApplyItemHitPhysics")] [HarmonyPostfix] private static void OnLocalPlayerWillApplyItemHitPhysicsPostfix(PlayerMovement __instance, ItemType itemType, float distance) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if (!LocalPlayerGuard.IsLocal(__instance)) { return; } HitSettings settings; if ((int)itemType != 10) { if ((int)itemType != 15) { if ((int)itemType != 16) { return; } settings = ShockHitHandler.GetRailgunSettings(); } else { if (!(distance <= GameManager.ItemSettings.ThunderstormLightningStrikeVaporizationRadius)) { return; } settings = ShockHitHandler.GetThunderstormSettings(); } } else { if (!(distance <= GameManager.ItemSettings.OrbitalLaserExplosionCenterRadius)) { return; } settings = ShockHitHandler.GetOrbitalLaserSettings(); } ShockHitHandler.HandleAnyHit(settings); } [HarmonyPatch(typeof(PlayerGolfer), "OnLocalPlayerWillBeEliminated")] [HarmonyPostfix] private static void OnLocalPlayerWillBeEliminatedPostfix(PlayerGolfer __instance, EliminationReason immediateEliminationReason) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) if (!LocalPlayerGuard.IsLocal(__instance)) { return; } EliminationShockHandler.ClearPending(); HitSettings settings; if ((int)immediateEliminationReason == 7) { if (Plugin.EnableFailedHoleShock.Value) { ShockDispatcher.Trigger("timed-out", Plugin.FailedHoleIntensity.Value, Plugin.FailedHoleDuration.Value, "timed out before finishing hole"); } else { ShockHitHandler.LogSkipped("hole timeout", "disabled"); } } else if (ShockHitHandler.TryGetInstantEliminationSettings(immediateEliminationReason, out settings)) { if (settings.Mode == Plugin.HitTriggerMode.KnockoutOnly) { ShockDispatcher.Trigger(settings.EventKey, settings.Intensity, settings.Duration, settings.Reason + " elimination"); } } else { EliminationShockHandler.HandleElimination(__instance.PlayerInfo, immediateEliminationReason); } } [HarmonyPatch(typeof(PlayerMovement), "OnRespawnStateChanged")] [HarmonyPostfix] private static void OnRespawnStateChangedPostfix(PlayerMovement __instance, RespawnState previousState, RespawnState currentState) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if (LocalPlayerGuard.IsLocal(__instance) && (int)previousState == 0 && (int)currentState == 1) { EliminationShockHandler.HandleRespawnStarted(__instance.PlayerInfo); } } [HarmonyPatch(typeof(PlayerMovement), "LocalPlayerBeginRespawn")] [HarmonyPrefix] private static void LocalPlayerBeginRespawnPrefix(PlayerMovement __instance, bool isRestart) { if (isRestart && LocalPlayerGuard.IsLocal(__instance)) { EliminationShockHandler.ClearPending(); } } } internal static class EliminationShockHandler { private static PlayerInfo? _pendingPlayer; private static EliminationReason _pendingReason; internal static void HandleElimination(PlayerInfo player, EliminationReason reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0032: 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) if ((int)reason == 6 || (int)reason == 23) { EliminationSettings settings = GetSettings(reason); if (!settings.Enabled) { ShockHitHandler.LogSkipped(settings.Reason, "death shock disabled"); return; } _pendingPlayer = player; _pendingReason = reason; NotificationService.Publish("Detected " + settings.Reason + "; waiting for confirmed local respawn.", Plugin.ShowSkippedNotifications.Value, "Waiting for respawn to start."); } } internal static void HandleRespawnStarted(PlayerInfo player) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_pendingPlayer == (Object)null || player != _pendingPlayer) { ClearPending(); return; } EliminationSettings settings = GetSettings(_pendingReason); ClearPending(); if (settings.Enabled) { ShockDispatcher.Trigger(settings.EventKey, settings.Intensity, settings.Duration, settings.Reason + " (respawn started)"); } } internal static void ClearPending() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) _pendingPlayer = null; _pendingReason = (EliminationReason)0; } private static EliminationSettings GetSettings(EliminationReason reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0070: Unknown result type (might be due to invalid IL or missing references) if ((int)reason != 6) { if ((int)reason == 23) { return new EliminationSettings("fog-death", "fell into fog", Plugin.EnableFogDeathShock.Value, Plugin.FogDeathIntensity.Value, Plugin.FogDeathDuration.Value); } throw new ArgumentOutOfRangeException("reason", reason, "Unsupported elimination reason."); } return new EliminationSettings("water-death", "fell into water", Plugin.EnableWaterDeathShock.Value, Plugin.WaterDeathIntensity.Value, Plugin.WaterDeathDuration.Value); } } internal static class LocalPlayerGuard { internal static bool IsLocal(PlayerMovement movement) { if ((Object)(object)movement != (Object)null && ((NetworkBehaviour)movement).isLocalPlayer && (Object)(object)GameManager.LocalPlayerInfo != (Object)null) { return movement.PlayerInfo == GameManager.LocalPlayerInfo; } return false; } internal static bool IsLocal(PlayerGolfer golfer) { if ((Object)(object)golfer != (Object)null && ((NetworkBehaviour)golfer).isLocalPlayer && (Object)(object)GameManager.LocalPlayerInfo != (Object)null) { return golfer.PlayerInfo == GameManager.LocalPlayerInfo; } return false; } } internal readonly struct EliminationSettings { internal string EventKey { get; } internal string Reason { get; } internal bool Enabled { get; } internal int Intensity { get; } internal int Duration { get; } internal EliminationSettings(string eventKey, string reason, bool enabled, int intensity, int duration) { EventKey = eventKey; Reason = reason; Enabled = enabled; Intensity = intensity; Duration = duration; } } internal static class ShockHitHandler { internal static void HandleAnyHit(HitSettings settings) { if (settings.Mode == Plugin.HitTriggerMode.AnyHit) { Handle(settings, wasApplied: false, isNewKnockout: false); } else if (settings.Mode == Plugin.HitTriggerMode.Disabled) { LogSkipped(settings.Reason, "disabled"); } } internal static void Handle(HitSettings settings, bool wasApplied, bool isNewKnockout) { if (settings.Mode == Plugin.HitTriggerMode.Disabled) { LogSkipped(settings.Reason, "disabled"); return; } if (settings.Mode == Plugin.HitTriggerMode.KnockoutOnly && (!wasApplied || !isNewKnockout)) { LogSkipped(settings.Reason, "no new knockout"); return; } int intensity = Mathf.Clamp(settings.Intensity, 1, 100); string text = ((wasApplied && isNewKnockout) ? "new knockout" : "hit without new knockout"); ShockDispatcher.Trigger(settings.EventKey, intensity, settings.Duration, settings.Reason + " (" + text + ")"); } internal static bool TryGetSettings(KnockoutType knockoutType, out HitSettings settings) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected I4, but got Unknown switch ((int)knockoutType) { case 0: settings = new HitSettings("club-hit", "club hit", Plugin.ClubHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 1: case 13: settings = new HitSettings("golf-ball-hit", "golf ball hit", Plugin.GolfBallHitMode.Value, Plugin.GolfBallHitIntensity.Value, Plugin.GolfBallHitDuration.Value); return true; case 4: case 15: settings = new HitSettings("dueling-pistol", "dueling pistol hit", Plugin.DuelingPistolHitMode.Value, Plugin.WeaponHitIntensity.Value, Plugin.WeaponHitDuration.Value); return true; case 5: case 16: settings = new HitSettings("elephant-gun", "elephant gun hit", Plugin.ElephantGunHitMode.Value, Plugin.WeaponHitIntensity.Value, Plugin.WeaponHitDuration.Value); return true; case 26: case 28: case 37: settings = GetRailgunSettings(); return true; case 7: case 14: settings = new HitSettings("rocket", "rocket hit", Plugin.RocketHitMode.Value, Plugin.ExplosiveHitIntensity.Value, Plugin.ExplosiveHitDuration.Value); return true; case 8: settings = new HitSettings("rocket-backblast", "rocket backblast hit", Plugin.RocketBackBlastHitMode.Value, Plugin.ExplosiveHitIntensity.Value, Plugin.ExplosiveHitDuration.Value); return true; case 12: settings = new HitSettings("landmine", "landmine hit", Plugin.LandmineHitMode.Value, Plugin.ExplosiveHitIntensity.Value, Plugin.ExplosiveHitDuration.Value); return true; case 17: case 25: case 32: case 35: settings = GetOrbitalLaserSettings(); return true; case 23: case 24: case 33: case 36: settings = GetThunderstormSettings(); return true; case 34: settings = new HitSettings("electromagnet-explosion", "electromagnet shield explosion", Plugin.ElectromagnetExplosionHitMode.Value, Plugin.ExplosiveHitIntensity.Value, Plugin.ExplosiveHitDuration.Value); return true; case 6: settings = new HitSettings("golf-cart", "golf cart hit", Plugin.GolfCartHitMode.Value, Plugin.GolfCartIntensity.Value, Plugin.GolfCartDuration.Value); return true; case 18: settings = new HitSettings("rocket-driver-swing", "Rocket Driver swing", Plugin.RocketDriverHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 19: settings = new HitSettings("rocket-driver-spin", "Rocket Driver spin hit", Plugin.RocketDriverHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 20: settings = new HitSettings("rocket-driver-projectile", "Rocket Driver projectile hit", Plugin.RocketDriverHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 29: settings = new HitSettings("jumbo-burger-swing", "Jumbo Burger giant swing", Plugin.JumboBurgerHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 30: settings = new HitSettings("jumbo-burger-projectile", "Jumbo Burger giant projectile hit", Plugin.JumboBurgerHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 31: settings = new HitSettings("jumbo-burger-collision", "Jumbo Burger giant collision", Plugin.JumboBurgerHitMode.Value, Plugin.ClubHitIntensity.Value, Plugin.ClubHitDuration.Value); return true; case 27: settings = new HitSettings("traffic-vehicle", "traffic vehicle hit", Plugin.TrafficVehicleHitMode.Value, Plugin.GolfCartIntensity.Value, Plugin.GolfCartDuration.Value); return true; default: settings = default(HitSettings); return false; } } internal static bool TryGetInstantEliminationSettings(EliminationReason reason, out HitSettings settings) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if ((int)reason != 24) { if ((int)reason != 32) { if ((int)reason == 34) { settings = GetRailgunSettings(); return true; } settings = default(HitSettings); return false; } settings = GetThunderstormSettings(); return true; } settings = GetOrbitalLaserSettings(); return true; } internal static HitSettings GetOrbitalLaserSettings() { return new HitSettings("orbital-laser", "orbital laser hit", Plugin.OrbitalLaserHitMode.Value, Plugin.ExplosiveHitIntensity.Value, Plugin.ExplosiveHitDuration.Value); } internal static HitSettings GetThunderstormSettings() { return new HitSettings("thunderstorm", "thunderstorm hit", Plugin.ThunderstormHitMode.Value, Plugin.ExplosiveHitIntensity.Value, Plugin.ExplosiveHitDuration.Value); } internal static HitSettings GetRailgunSettings() { return new HitSettings("railgun", "railgun hit", Plugin.RailgunHitMode.Value, Plugin.WeaponHitIntensity.Value, Plugin.WeaponHitDuration.Value); } internal static void LogSkipped(string reason, string explanation) { NotificationService.Publish("Ignored " + reason + ": " + explanation + ".", Plugin.ShowSkippedNotifications.Value, "Shock skipped: " + explanation + "."); } } internal readonly struct HitSettings { internal string EventKey { get; } internal string Reason { get; } internal Plugin.HitTriggerMode Mode { get; } internal int Intensity { get; } internal int Duration { get; } internal HitSettings(string eventKey, string reason, Plugin.HitTriggerMode mode, int intensity, int duration) { EventKey = eventKey; Reason = reason; Mode = mode; Intensity = intensity; Duration = duration; } } internal sealed class ShockRequestQueue : IDisposable { private const int MaximumPendingRequests = 1; private readonly CancellationTokenSource _cancellation = new CancellationTokenSource(); private readonly object _gate = new object(); private readonly SemaphoreSlim _signal = new SemaphoreSlim(0); private readonly ConcurrentQueue> _tasks = new ConcurrentQueue>(); private readonly Task _worker; private int _pendingCount; private bool _stopping; internal ShockRequestQueue() { _worker = Task.Run((Func)ProcessQueueAsync); } internal bool TryEnqueue(Func task) { lock (_gate) { if (_stopping || _pendingCount >= 1) { return false; } _tasks.Enqueue(task); _pendingCount++; _signal.Release(); return true; } } private async Task ProcessQueueAsync() { _ = 1; try { while (true) { await _signal.WaitAsync(_cancellation.Token); if (!_tasks.TryDequeue(out Func result)) { continue; } try { await result(_cancellation.Token); } catch (OperationCanceledException) when (_cancellation.IsCancellationRequested) { break; } catch (Exception ex2) { NotificationService.PublishError("Queued shock request failed: " + ex2.Message); Plugin.Log.LogError((object)ex2); } finally { lock (_gate) { _pendingCount--; } } } } catch (OperationCanceledException) when (_cancellation.IsCancellationRequested) { } } public void Dispose() { lock (_gate) { if (_stopping) { return; } _stopping = true; _cancellation.Cancel(); } try { _worker.GetAwaiter().GetResult(); } catch (OperationCanceledException) { } finally { _cancellation.Dispose(); _signal.Dispose(); Func result; while (_tasks.TryDequeue(out result)) { } } } } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ConstantExpectedAttribute : Attribute { public object? Min { get; set; } public object? Max { get; set; } } [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ExperimentalAttribute : Attribute { public string DiagnosticId { get; } public string? UrlFormat { get; set; } public ExperimentalAttribute(string diagnosticId) { DiagnosticId = diagnosticId; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullAttribute : Attribute { public string[] Members { get; } public MemberNotNullAttribute(string member) { Members = new string[1] { member }; } public MemberNotNullAttribute(params string[] members) { Members = members; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public string[] Members { get; } public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; Members = new string[1] { member }; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { ReturnValue = returnValue; Members = members; } } [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SetsRequiredMembersAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class StringSyntaxAttribute : Attribute { public const string CompositeFormat = "CompositeFormat"; public const string DateOnlyFormat = "DateOnlyFormat"; public const string DateTimeFormat = "DateTimeFormat"; public const string EnumFormat = "EnumFormat"; public const string GuidFormat = "GuidFormat"; public const string Json = "Json"; public const string NumericFormat = "NumericFormat"; public const string Regex = "Regex"; public const string TimeOnlyFormat = "TimeOnlyFormat"; public const string TimeSpanFormat = "TimeSpanFormat"; public const string Uri = "Uri"; public const string Xml = "Xml"; public string Syntax { get; } public object?[] Arguments { get; } public StringSyntaxAttribute(string syntax) { Syntax = syntax; Arguments = new object[0]; } public StringSyntaxAttribute(string syntax, params object?[] arguments) { Syntax = syntax; Arguments = arguments; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class UnscopedRefAttribute : Attribute { } } namespace System.Runtime.Versioning { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiresPreviewFeaturesAttribute : Attribute { public string? Message { get; } public string? Url { get; set; } public RequiresPreviewFeaturesAttribute() { } public RequiresPreviewFeaturesAttribute(string? message) { Message = message; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CallerArgumentExpressionAttribute : Attribute { public string ParameterName { get; } public CallerArgumentExpressionAttribute(string parameterName) { ParameterName = parameterName; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CollectionBuilderAttribute : Attribute { public Type BuilderType { get; } public string MethodName { get; } public CollectionBuilderAttribute(Type builderType, string methodName) { BuilderType = builderType; MethodName = methodName; } } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = "RefStructs"; public const string RequiredMembers = "RequiredMembers"; public string FeatureName { get; } public bool IsOptional { get; set; } public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public string[] Arguments { get; } public InterpolatedStringHandlerArgumentAttribute(string argument) { Arguments = new string[1] { argument }; } public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { Arguments = arguments; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerAttribute : Attribute { } [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal static class IsExternalInit { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ModuleInitializerAttribute : Attribute { } [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class OverloadResolutionPriorityAttribute : Attribute { public int Priority { get; } public OverloadResolutionPriorityAttribute(int priority) { Priority = priority; } } [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] [ExcludeFromCodeCoverage] internal sealed class ParamCollectionAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal sealed class RequiresLocationAttribute : Attribute { } [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SkipLocalsInitAttribute : Attribute { } }