Decompiled source of VibingBattleGolf v0.4.25

BepInEx\plugins\VibingYorunyan-VibingBattleGolf\com.vegancat.vibingbattlegolf.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
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 UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Networking;
using VibingBattleGolf.Config;
using VibingBattleGolf.Lovense;
using VibingBattleGolf.Patches;
using VibingBattleGolf.Services;
using VibingBattleGolf.Tips;
using VibingBattleGolf.UI;

[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.vegancat.vibingbattlegolf")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.25.0")]
[assembly: AssemblyInformationalVersion("0.4.25+6ba6d9d2931833bdc27fb2d4d3f75ff874986e46")]
[assembly: AssemblyProduct("Vibing Battle Golf")]
[assembly: AssemblyTitle("com.vegancat.vibingbattlegolf")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.4.25.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 VibingBattleGolf
{
	[BepInPlugin("com.vegancat.vibingbattlegolf", "Vibing Battle Golf", "0.4.25")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		private LovenseClient _client;

		private TipController _tips;

		internal static ManualLogSource Log { get; private set; }

		internal static VibeController Vibes { get; private set; }

		internal static LovenseClient Lovense { get; private set; }

		internal static TipBridgeServer TipBridge { get; private set; }

		internal static OrbitalLaserVibeController OrbitalLaserVibes { get; private set; }

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_0027: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig modConfig = ModConfig.Load(((BaseUnityPlugin)this).Config);
			GameObject val = new GameObject("VibingBattleGolfTracker");
			Object.DontDestroyOnLoad((Object)val);
			LovenseRequestQueue lovenseRequestQueue = val.AddComponent<LovenseRequestQueue>();
			VibeActivationTimer vibeActivationTimer = val.AddComponent<VibeActivationTimer>();
			_client = new LovenseClient(Log, modConfig.LovenseBaseUrl.Value, modConfig.LovensePlatformName.Value, modConfig.LovenseAutoDiscover.Value, lovenseRequestQueue);
			lovenseRequestQueue.Initialize(_client);
			vibeActivationTimer.Initialize(_client);
			_client.SetTimer(vibeActivationTimer);
			Lovense = _client;
			Vibes = new VibeController(Log, _client);
			_tips = new TipController(Log, _client);
			TipBridgeServer tipBridgeServer = val.AddComponent<TipBridgeServer>();
			tipBridgeServer.Initialize(Log, _tips);
			TipBridge = tipBridgeServer;
			OrbitalLaserVibeController orbitalLaserVibeController = val.AddComponent<OrbitalLaserVibeController>();
			orbitalLaserVibeController.Initialize(Log, _client);
			OrbitalLaserVibes = orbitalLaserVibeController;
			_harmony = new Harmony("com.vegancat.vibingbattlegolf");
			ApplyHarmonyPatches();
			val.AddComponent<LocalPlayerTracker>();
			val.AddComponent<TestHotkeyComponent>();
			val.AddComponent<SettingsGuiComponent>();
			_client.DiscoverAsync();
			Log.LogInfo((object)"Vibing Battle Golf v0.4.25 loaded (built for Super Battle Golf Steam update 2026-07-29 / SBG 1.2+).");
		}

		private void ApplyHarmonyPatches()
		{
			Type[] array = new Type[5]
			{
				typeof(PlayerMovementPatches),
				typeof(PlayerInventoryPatches),
				typeof(PlayerInfoPatches),
				typeof(PlayerGolferPatches),
				typeof(OrbitalLaserPatches)
			};
			foreach (Type type in array)
			{
				try
				{
					_harmony.CreateClassProcessor(type).Patch();
					Log.LogDebug((object)("Harmony patched " + type.Name));
				}
				catch (Exception ex)
				{
					Log.LogError((object)("Harmony failed to patch " + type.Name + ": " + ex.Message));
				}
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_client?.Dispose();
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "com.vegancat.vibingbattlegolf";

		public const string PLUGIN_NAME = "Vibing Battle Golf";

		public const string PLUGIN_VERSION = "0.4.25";
	}
}
namespace VibingBattleGolf.UI
{
	internal sealed class SettingsGuiComponent : MonoBehaviour
	{
		private const int WindowWidth = 460;

		private const int WindowHeight = 600;

		private static readonly string[] TabLabels = new string[4] { "Events", "Items", "Tips", "Settings" };

		private bool _visible;

		private int _tab;

		private Vector2 _eventsScroll;

		private Vector2 _itemsScroll;

		private Vector2 _tipsScroll;

		private Rect _windowRect;

		private string _statusMessage = "";

		private float _statusExpires;

		private GUIStyle _headerStyle;

		private GUIStyle _toggleStyle;

		private bool _stylesReady;

		private bool _showModelToken;

		private string _openPresetKey;

		private void Awake()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			_windowRect = new Rect(20f, 80f, 460f, 600f);
		}

		private void Update()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				return;
			}
			if (((ButtonControl)current[ModConfig.Instance.SettingsGuiKey.Value]).wasPressedThisFrame)
			{
				_visible = !_visible;
				if (_visible)
				{
					ResetWindowSize();
				}
			}
			if (_statusExpires > 0f && Time.unscaledTime > _statusExpires)
			{
				_statusMessage = "";
				_statusExpires = 0f;
			}
		}

		private void OnGUI()
		{
			//IL_0016: 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_0031: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (_visible)
			{
				EnsureStyles();
				_windowRect = GUI.Window(78257, _windowRect, new WindowFunction(DrawWindow), "Vibing Battle Golf");
				ResetWindowSize();
			}
		}

		private void DrawWindow(int id)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			DrawMasterRow();
			DrawTabBar();
			GUILayout.Space(4f);
			switch (_tab)
			{
			case 0:
				DrawEventsTab();
				break;
			case 1:
				DrawItemsTab();
				break;
			case 2:
				DrawTipsTab();
				break;
			default:
				DrawSettingsTab();
				break;
			}
			if (!string.IsNullOrEmpty(_statusMessage))
			{
				GUILayout.Label(_statusMessage, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(8f);
			GUILayout.Label($"Press {ModConfig.Instance.SettingsGuiKey.Value} to close", GUI.skin.label, Array.Empty<GUILayoutOption>());
			GUILayout.EndVertical();
			GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width, 24f));
		}

		private void ResetWindowSize()
		{
			((Rect)(ref _windowRect)).width = 460f;
			((Rect)(ref _windowRect)).height = 600f;
		}

		private void DrawMasterRow()
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			ModConfig instance = ModConfig.Instance;
			bool flag = GUILayout.Toggle(instance.MasterEnabled.Value, " Master Enable", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag != instance.MasterEnabled.Value)
			{
				instance.MasterEnabled.Value = flag;
				instance.Save();
			}
			TipConfig tips = instance.Tips;
			if (tips.Enabled.Value)
			{
				bool flag2 = GUILayout.Toggle(tips.ReactToTips.Value, " Tip React", _toggleStyle, Array.Empty<GUILayoutOption>());
				if (flag2 != tips.ReactToTips.Value)
				{
					tips.ReactToTips.Value = flag2;
					instance.Save();
				}
			}
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Test Vibe", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
			{
				Plugin.Vibes.Trigger(VibeEvent.TestPing);
				SetStatus("Test vibe sent.");
			}
			GUILayout.EndHorizontal();
		}

		private void DrawTabBar()
		{
			_tab = GUILayout.Toolbar(_tab, TabLabels, Array.Empty<GUILayoutOption>());
		}

		private void DrawEventsTab()
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: 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)
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Enable All", Array.Empty<GUILayoutOption>()))
			{
				ModConfig.Instance.SetAllEventsEnabled(enabled: true);
				SetStatus("All events enabled.");
			}
			if (GUILayout.Button("Disable All", Array.Empty<GUILayoutOption>()))
			{
				ModConfig.Instance.SetAllEventsEnabled(enabled: false);
				SetStatus("All events disabled.");
			}
			GUILayout.EndHorizontal();
			GUILayout.Space(4f);
			_eventsScroll = GUILayout.BeginScrollView(_eventsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(360f) });
			foreach (EventGuiEntry eventEntry in ModConfig.Instance.EventEntries)
			{
				DrawEventRow(eventEntry);
			}
			DrawOrbitalLaserSection();
			GUILayout.EndScrollView();
		}

		private void DrawOrbitalLaserSection()
		{
			OrbitalLaserConfig orbitalLaser = ModConfig.Instance.OrbitalLaser;
			GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
			GUILayout.Label("Orbital Laser (special)", _headerStyle, Array.Empty<GUILayoutOption>());
			bool flag = GUILayout.Toggle(orbitalLaser.Enabled.Value, " Enabled (charge ramp + max hit)", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag != orbitalLaser.Enabled.Value)
			{
				orbitalLaser.Enabled.Value = flag;
				ModConfig.Instance.Save();
			}
			DrawStrengthSlider("Start", orbitalLaser.ChargeStartStrength);
			DrawStrengthSlider("End", orbitalLaser.ChargeEndStrength);
			DrawStrengthSlider("Hit", orbitalLaser.HitStrength);
			DrawDurationSlider("Dur", orbitalLaser.HitDuration);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Test start", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }))
			{
				Plugin.Vibes.Preview(orbitalLaser.ChargeStartStrength.Value, 1.5f, "", "Orbital charge start");
				SetStatus("Test orbital start " + StrengthScale.Format(orbitalLaser.ChargeStartStrength.Value) + " for 1.5s");
			}
			if (GUILayout.Button("Test end", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) }))
			{
				Plugin.Vibes.Preview(orbitalLaser.ChargeEndStrength.Value, 1.5f, "", "Orbital charge end");
				SetStatus("Test orbital end " + StrengthScale.Format(orbitalLaser.ChargeEndStrength.Value) + " for 1.5s");
			}
			if (GUILayout.Button("Test hit", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) }))
			{
				Plugin.Vibes.Preview(orbitalLaser.HitStrength.Value, orbitalLaser.HitDuration.Value, "", "Orbital hit");
				SetStatus($"Test orbital hit {StrengthScale.Format(orbitalLaser.HitStrength.Value)} for {orbitalLaser.HitDuration.Value:0.##}s");
			}
			GUILayout.EndHorizontal();
			bool flag2 = GUILayout.Toggle(orbitalLaser.CancelMaxIfNotDestroyed.Value, " Max only if KO/eliminated", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag2 != orbitalLaser.CancelMaxIfNotDestroyed.Value)
			{
				orbitalLaser.CancelMaxIfNotDestroyed.Value = flag2;
				ModConfig.Instance.Save();
			}
			GUILayout.EndVertical();
			GUILayout.Space(4f);
		}

		private void DrawStrengthSlider(string label, ConfigEntry<int> entry)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (!string.IsNullOrEmpty(label))
			{
				GUILayout.Label(label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) });
			}
			int num = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)StrengthScale.ToPercent(entry.Value), 0f, 100f, Array.Empty<GUILayoutOption>()) / 5f) * 5;
			num = Mathf.Clamp(num, 0, 100);
			GUILayout.Label($"{num}%", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) });
			GUILayout.EndHorizontal();
			int num2 = StrengthScale.FromPercent(num);
			if (num2 != entry.Value)
			{
				entry.Value = num2;
				ModConfig.Instance.Save();
			}
		}

		private void DrawDurationSlider(string label, ConfigEntry<float> entry)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (!string.IsNullOrEmpty(label))
			{
				GUILayout.Label(label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) });
			}
			float num = DurationScale.Clamp(GUILayout.HorizontalSlider(entry.Value, 0.1f, 120f, Array.Empty<GUILayoutOption>()));
			GUILayout.Label(DurationScale.Format(num), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) });
			GUILayout.EndHorizontal();
			if (!Mathf.Approximately(num, entry.Value))
			{
				entry.Value = num;
				ModConfig.Instance.Save();
			}
		}

		private void DrawEventRow(EventGuiEntry entry)
		{
			EventConfig config = entry.Config;
			GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag = GUILayout.Toggle(config.Enabled.Value, " " + entry.Label, _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag != config.Enabled.Value)
			{
				config.Enabled.Value = flag;
				ModConfig.Instance.Save();
			}
			GUILayout.EndHorizontal();
			DrawStrengthSlider("Str", config.Strength);
			DrawDurationSlider("Dur", config.Duration);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Test", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }))
			{
				Plugin.Vibes.Preview(config, entry.Label);
				SetStatus("Test " + entry.Label + ": " + StrengthScale.Format(config.Strength.Value) + " for " + DurationScale.Format(config.Duration.Value));
			}
			GUILayout.EndHorizontal();
			DrawPresetDropdown("event:" + entry.Label, config.Preset);
			GUILayout.EndVertical();
			GUILayout.Space(2f);
		}

		private void DrawItemsTab()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			_itemsScroll = GUILayout.BeginScrollView(_itemsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(390f) });
			foreach (KeyValuePair<ItemType, ItemVibeConfig> item in ModConfig.Instance.Items)
			{
				DrawItemRow(item.Value);
			}
			GUILayout.EndScrollView();
		}

		private void DrawItemRow(ItemVibeConfig item)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: 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)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
			GUILayout.Label(((object)item.ItemType/*cast due to .constrained prefix*/).ToString(), _headerStyle, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag = GUILayout.Toggle(item.UseEnabled.Value, " Use", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag != item.UseEnabled.Value)
			{
				item.UseEnabled.Value = flag;
				ModConfig.Instance.Save();
			}
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Test", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) }))
			{
				Plugin.Vibes.Preview(item.UseStrength.Value, item.UseDuration.Value, item.UsePreset.Value, $"{item.ItemType} use");
				SetStatus($"Test {item.ItemType} use: {StrengthScale.Format(item.UseStrength.Value)} for {DurationScale.Format(item.UseDuration.Value)}");
			}
			GUILayout.EndHorizontal();
			DrawStrengthSlider("Str", item.UseStrength);
			DrawDurationSlider("Dur", item.UseDuration);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag2 = GUILayout.Toggle(item.HitEnabled.Value, " Hit", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag2 != item.HitEnabled.Value)
			{
				item.HitEnabled.Value = flag2;
				ModConfig.Instance.Save();
			}
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Test", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) }))
			{
				Plugin.Vibes.Preview(item.HitStrength.Value, item.HitDuration.Value, item.HitPreset.Value, $"{item.ItemType} hit");
				SetStatus($"Test {item.ItemType} hit: {StrengthScale.Format(item.HitStrength.Value)} for {DurationScale.Format(item.HitDuration.Value)}");
			}
			GUILayout.EndHorizontal();
			DrawStrengthSlider("Str", item.HitStrength);
			DrawDurationSlider("Dur", item.HitDuration);
			DrawPresetDropdown($"item:{item.ItemType}:use", item.UsePreset, "Use pattern");
			DrawPresetDropdown($"item:{item.ItemType}:hit", item.HitPreset, "Hit pattern");
			GUILayout.EndVertical();
			GUILayout.Space(2f);
		}

		private void DrawPresetDropdown(string key, ConfigEntry<string> entry, string label = "Preset")
		{
			int num = LovensePresets.IndexOf(entry.Value);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) });
			if (GUILayout.Button(LovensePresets.Labels[num], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }))
			{
				_openPresetKey = ((_openPresetKey == key) ? null : key);
			}
			GUILayout.EndHorizontal();
			if (!(_openPresetKey != key))
			{
				int num2 = GUILayout.SelectionGrid(num, LovensePresets.Labels, 3, Array.Empty<GUILayoutOption>());
				if (num2 != num)
				{
					entry.Value = LovensePresets.Values[num2];
					ModConfig.Instance.Save();
					_openPresetKey = null;
				}
			}
		}

		private void DrawTipsTab()
		{
			//IL_0653: Unknown result type (might be due to invalid IL or missing references)
			//IL_066b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0670: Unknown result type (might be due to invalid IL or missing references)
			TipConfig tips = ModConfig.Instance.Tips;
			string text = (TipFeed.BridgeConnected ? "Connected" : "Waiting for tip-bridge.html");
			GUILayout.Label("Bridge: " + text, _headerStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label($"Relay: http://127.0.0.1:{tips.RelayPort.Value}/tip", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Lovense mToken", _headerStyle, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (_showModelToken)
			{
				string text2 = GUILayout.TextField(tips.ModelToken.Value ?? "", Array.Empty<GUILayoutOption>());
				if (!string.Equals(text2, tips.ModelToken.Value, StringComparison.Ordinal))
				{
					tips.ModelToken.Value = text2;
					ModConfig.Instance.Save();
				}
			}
			else
			{
				GUILayout.Label(string.IsNullOrWhiteSpace(tips.ModelToken.Value) ? "No token saved" : "Token saved", Array.Empty<GUILayoutOption>());
			}
			if (GUILayout.Button(_showModelToken ? "Hide" : "Edit", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) }))
			{
				_showModelToken = !_showModelToken;
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Open Tip Bridge", Array.Empty<GUILayoutOption>()))
			{
				if (TipBridgeLauncher.TryOpen(Plugin.Log, out var error))
				{
					SetStatus("Tip bridge opened in browser.");
				}
				else
				{
					SetStatus(error ?? "Could not open tip bridge.");
				}
			}
			if (GUILayout.Button("Save token", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
			{
				ModConfig.Instance.Save();
				SetStatus(string.IsNullOrWhiteSpace(tips.ModelToken.Value) ? "Token cleared." : "Token saved to config.");
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag = GUILayout.Toggle(tips.Enabled.Value, " Tip integration enabled", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag != tips.Enabled.Value)
			{
				tips.Enabled.Value = flag;
				ModConfig.Instance.Save();
				Plugin.TipBridge.RestartListener();
			}
			if (GUILayout.Button("Restart relay", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }))
			{
				Plugin.TipBridge.RestartListener();
				SetStatus("Tip relay restarted.");
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag2 = GUILayout.Toggle(tips.ReactToTips.Value, " React to tips (vibes + block golf)", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag2 != tips.ReactToTips.Value)
			{
				tips.ReactToTips.Value = flag2;
				ModConfig.Instance.Save();
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Mode", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) });
			int value = (int)tips.Mode.Value;
			int num = GUILayout.SelectionGrid(value, new string[3] { "Pass", "Mirror", "Bonus" }, 3, Array.Empty<GUILayoutOption>());
			if (num != value)
			{
				tips.Mode.Value = (TipIntegrationMode)num;
				ModConfig.Instance.Save();
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag3 = GUILayout.Toggle(tips.BlockGameDuringTips.Value, " Block golf vibes during tips", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag3 != tips.BlockGameDuringTips.Value)
			{
				tips.BlockGameDuringTips.Value = flag3;
				ModConfig.Instance.Save();
			}
			bool flag4 = GUILayout.Toggle(tips.UseConnectLevels.Value, " Use Connect levels", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag4 != tips.UseConnectLevels.Value)
			{
				tips.UseConnectLevels.Value = flag4;
				ModConfig.Instance.Save();
			}
			GUILayout.EndHorizontal();
			GUILayout.Label("Tip tiers", _headerStyle, Array.Empty<GUILayoutOption>());
			DrawStrengthSlider("T1", tips.Tier1Strength);
			DrawDurationSlider("T1", tips.Tier1Duration);
			DrawStrengthSlider("T2", tips.Tier2Strength);
			DrawDurationSlider("T2", tips.Tier2Duration);
			DrawStrengthSlider("T3", tips.Tier3Strength);
			DrawDurationSlider("T3", tips.Tier3Duration);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button($"Test T1 {tips.Tier1Duration.Value:0.#}s", Array.Empty<GUILayoutOption>()))
			{
				Plugin.Vibes.Preview(tips.Tier1Strength.Value, tips.Tier1Duration.Value, tips.Tier1Preset.Value, "Tip tier 1");
				SetStatus($"Test tip tier 1: {StrengthScale.Format(tips.Tier1Strength.Value)} for {tips.Tier1Duration.Value:0.##}s");
			}
			if (GUILayout.Button($"Test T2 {tips.Tier2Duration.Value:0.#}s", Array.Empty<GUILayoutOption>()))
			{
				Plugin.Vibes.Preview(tips.Tier2Strength.Value, tips.Tier2Duration.Value, tips.Tier2Preset.Value, "Tip tier 2");
				SetStatus($"Test tip tier 2: {StrengthScale.Format(tips.Tier2Strength.Value)} for {tips.Tier2Duration.Value:0.##}s");
			}
			if (GUILayout.Button($"Test T3 {tips.Tier3Duration.Value:0.#}s", Array.Empty<GUILayoutOption>()))
			{
				Plugin.Vibes.Preview(tips.Tier3Strength.Value, tips.Tier3Duration.Value, tips.Tier3Preset.Value, "Tip tier 3");
				SetStatus($"Test tip tier 3: {StrengthScale.Format(tips.Tier3Strength.Value)} for {tips.Tier3Duration.Value:0.##}s");
			}
			GUILayout.EndHorizontal();
			DrawPresetDropdown("tip:t1", tips.Tier1Preset, "T1 pattern");
			DrawPresetDropdown("tip:t2", tips.Tier2Preset, "T2 pattern");
			DrawPresetDropdown("tip:t3", tips.Tier3Preset, "T3 pattern");
			GUILayout.Label("Recent tips", _headerStyle, Array.Empty<GUILayoutOption>());
			_tipsScroll = GUILayout.BeginScrollView(_tipsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(160f) });
			if (TipFeed.RecentTips.Count == 0)
			{
				GUILayout.Label("No tips yet. Open tip-bridge.html in your browser.", Array.Empty<GUILayoutOption>());
			}
			else
			{
				foreach (TipEvent recentTip in TipFeed.RecentTips)
				{
					GUILayout.Label(recentTip.Summary, Array.Empty<GUILayoutOption>());
				}
			}
			GUILayout.EndScrollView();
		}

		private void DrawSettingsTab()
		{
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			ModConfig instance = ModConfig.Instance;
			GUILayout.Label("Lovense", _headerStyle, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("URL", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) });
			string text = GUILayout.TextField(instance.LovenseBaseUrl.Value, Array.Empty<GUILayoutOption>());
			if (!string.Equals(text, instance.LovenseBaseUrl.Value, StringComparison.Ordinal))
			{
				instance.LovenseBaseUrl.Value = text;
				instance.Save();
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			bool flag = GUILayout.Toggle(instance.LovenseAutoDiscover.Value, " Auto-discover endpoints", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag != instance.LovenseAutoDiscover.Value)
			{
				instance.LovenseAutoDiscover.Value = flag;
				instance.Save();
			}
			GUILayout.EndHorizontal();
			if (GUILayout.Button("Reconnect Lovense", Array.Empty<GUILayoutOption>()))
			{
				Plugin.Lovense.DiscoverAsync().ContinueWith(delegate(Task<bool> task)
				{
					SetStatus(task.Result ? "Lovense connected." : "Lovense not reachable.");
				});
			}
			GUILayout.Space(8f);
			GUILayout.Label("Hotkeys", _headerStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label($"Settings panel: {instance.SettingsGuiKey.Value}", Array.Empty<GUILayoutOption>());
			GUILayout.Label($"Test vibe: {instance.TestVibeKey.Value}", Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			bool flag2 = GUILayout.Toggle(instance.HoleInOneReplacesScored.Value, " Hole-in-one replaces scored event", _toggleStyle, Array.Empty<GUILayoutOption>());
			if (flag2 != instance.HoleInOneReplacesScored.Value)
			{
				instance.HoleInOneReplacesScored.Value = flag2;
				instance.Save();
			}
		}

		private void SetStatus(string message)
		{
			_statusMessage = message;
			_statusExpires = Time.unscaledTime + 3f;
		}

		private void EnsureStyles()
		{
			//IL_0014: 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_0025: Expected O, but got Unknown
			//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_0042: Expected O, but got Unknown
			if (!_stylesReady)
			{
				_headerStyle = new GUIStyle(GUI.skin.label)
				{
					fontStyle = (FontStyle)1
				};
				_toggleStyle = new GUIStyle(GUI.skin.toggle)
				{
					fontSize = 12
				};
				_stylesReady = true;
			}
		}
	}
}
namespace VibingBattleGolf.Tips
{
	internal static class TipBridgeLauncher
	{
		public static bool TryGetBridgePath(out string path)
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			path = Path.Combine(directoryName ?? "", "tip-bridge", "tip-bridge.html");
			return File.Exists(path);
		}

		public static bool TryOpen(ManualLogSource log, out string error)
		{
			error = null;
			if (!TryGetBridgePath(out var path))
			{
				error = "tip-bridge.html not found next to the mod DLL.";
				return false;
			}
			TipConfig tips = ModConfig.Instance.Tips;
			string text = BuildHashFragment(tips.ModelToken.Value, tips.RelayPort.Value, tips.RelaySecret.Value);
			string fileName = new Uri(Path.GetFullPath(path)).AbsoluteUri + "#" + text;
			try
			{
				Process.Start(new ProcessStartInfo
				{
					FileName = fileName,
					UseShellExecute = true
				});
				log.LogInfo((object)("Opened tip bridge: " + path));
				return true;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				log.LogWarning((object)("Failed to open tip bridge: " + ex.Message));
				return false;
			}
		}

		private static string BuildHashFragment(string modelToken, int relayPort, string relaySecret)
		{
			List<string> list = new List<string>
			{
				$"relayPort={relayPort}",
				"autoconnect=1"
			};
			if (!string.IsNullOrWhiteSpace(modelToken))
			{
				list.Add("mToken=" + Uri.EscapeDataString(modelToken.Trim()));
			}
			if (!string.IsNullOrWhiteSpace(relaySecret))
			{
				list.Add("relaySecret=" + Uri.EscapeDataString(relaySecret.Trim()));
			}
			return string.Join("&", list);
		}
	}
	internal sealed class TipBridgeServer : MonoBehaviour
	{
		private readonly ConcurrentQueue<string> _pendingPayloads = new ConcurrentQueue<string>();

		private HttpListener _listener;

		private Thread _listenerThread;

		private volatile bool _running;

		private ManualLogSource _log;

		private TipController _controller;

		private int _activePort;

		public void Initialize(ManualLogSource log, TipController controller)
		{
			_log = log;
			_controller = controller;
			RestartListener();
		}

		private void Update()
		{
			string result;
			while (_pendingPayloads.TryDequeue(out result))
			{
				_controller?.HandlePayload(result);
			}
		}

		private void OnDestroy()
		{
			StopListener();
		}

		public void RestartListener()
		{
			StopListener();
			TipConfig tips = ModConfig.Instance.Tips;
			if (!tips.Enabled.Value)
			{
				TipFeed.MarkBridgeDisconnected();
				return;
			}
			int num = Mathf.Clamp(tips.RelayPort.Value, 1024, 65535);
			try
			{
				_listener = new HttpListener();
				_listener.Prefixes.Add($"http://127.0.0.1:{num}/");
				_listener.Start();
				_running = true;
				_activePort = num;
				_listenerThread = new Thread(ListenLoop)
				{
					IsBackground = true,
					Name = "VibingBattleGolfTipBridge"
				};
				_listenerThread.Start();
				_log.LogInfo((object)$"Tip bridge listening on http://127.0.0.1:{num}/tip");
			}
			catch (Exception ex)
			{
				_log.LogError((object)$"Tip bridge failed to start on port {num}: {ex.Message}");
				TipFeed.MarkBridgeDisconnected();
			}
		}

		private void ListenLoop()
		{
			while (_running)
			{
				try
				{
					HttpListenerContext context = _listener.GetContext();
					HandleRequest(context);
				}
				catch (HttpListenerException) when (!_running)
				{
					break;
				}
				catch (ObjectDisposedException) when (!_running)
				{
					break;
				}
				catch (Exception ex3)
				{
					_log.LogWarning((object)("Tip bridge request error: " + ex3.Message));
				}
			}
		}

		private void HandleRequest(HttpListenerContext context)
		{
			HttpListenerRequest request = context.Request;
			HttpListenerResponse response = context.Response;
			AddCorsHeaders(response);
			if (request.HttpMethod == "OPTIONS")
			{
				WriteResponse(response, 204, "");
				return;
			}
			string text = request.Url.AbsolutePath.TrimEnd('/');
			if (text.Equals("/health", StringComparison.OrdinalIgnoreCase))
			{
				_pendingPayloads.Enqueue("{\"type\":\"bridge-ping\"}");
				WriteResponse(response, 200, "{\"ok\":true}");
				return;
			}
			if (!text.Equals("/tip", StringComparison.OrdinalIgnoreCase) || !request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
			{
				WriteResponse(response, 404, "{\"error\":\"not found\"}");
				return;
			}
			if (!Authorize(request))
			{
				WriteResponse(response, 401, "{\"error\":\"unauthorized\"}");
				return;
			}
			string text2;
			using (StreamReader streamReader = new StreamReader(request.InputStream, request.ContentEncoding ?? Encoding.UTF8))
			{
				text2 = streamReader.ReadToEnd();
			}
			if (!string.IsNullOrWhiteSpace(text2))
			{
				_pendingPayloads.Enqueue(text2);
			}
			WriteResponse(response, 200, "{\"ok\":true}");
		}

		private bool Authorize(HttpListenerRequest request)
		{
			string value = ModConfig.Instance.Tips.RelaySecret.Value;
			if (string.IsNullOrWhiteSpace(value))
			{
				return true;
			}
			return string.Equals(request.Headers["X-Vibing-Secret"], value, StringComparison.Ordinal);
		}

		private static void AddCorsHeaders(HttpListenerResponse response)
		{
			response.Headers.Add("Access-Control-Allow-Origin", "*");
			response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
			response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-Vibing-Secret");
		}

		private static void WriteResponse(HttpListenerResponse response, int statusCode, string body)
		{
			response.StatusCode = statusCode;
			byte[] bytes = Encoding.UTF8.GetBytes(body);
			response.ContentType = "application/json";
			response.ContentLength64 = bytes.Length;
			response.OutputStream.Write(bytes, 0, bytes.Length);
			response.OutputStream.Close();
		}

		private void StopListener()
		{
			_running = false;
			if (_listener != null)
			{
				try
				{
					_listener.Stop();
					_listener.Close();
				}
				catch
				{
				}
				_listener = null;
			}
			_listenerThread = null;
			TipFeed.MarkBridgeDisconnected();
		}
	}
	internal sealed class TipController
	{
		private readonly struct TipVibeProfile
		{
			public readonly string Tipper;

			public readonly int Amount;

			public readonly float Duration;

			public readonly int Strength;

			public readonly string Preset;

			public TipVibeProfile(string tipper, int amount, float duration, int strength, string preset)
			{
				Tipper = tipper;
				Amount = amount;
				Duration = duration;
				Strength = strength;
				Preset = preset;
			}
		}

		private readonly ManualLogSource _log;

		private readonly LovenseClient _client;

		public TipController(ManualLogSource log, LovenseClient client)
		{
			_log = log;
			_client = client;
		}

		public void HandlePayload(string json)
		{
			if (json.IndexOf("bridge-ping", StringComparison.Ordinal) >= 0)
			{
				TipFeed.RecordBridgePing(Time.unscaledTime);
				return;
			}
			ModConfig instance = ModConfig.Instance;
			if (!instance.MasterEnabled.Value || !instance.Tips.Enabled.Value)
			{
				return;
			}
			TipFeed.RecordBridgePing(Time.unscaledTime);
			ParsedTipPayload parsed = TipJsonParser.Parse(json);
			if (!parsed.Found)
			{
				_log.LogDebug((object)"Tip payload ignored (no tip fields found).");
				return;
			}
			TipVibeProfile profile = BuildProfile(parsed);
			TipEvent tip = new TipEvent(profile.Tipper, profile.Amount, profile.Duration, profile.Strength, profile.Preset, Time.unscaledTime);
			TipFeed.RecordTip(tip);
			if (!instance.Tips.ReactToTips.Value)
			{
				_log.LogInfo((object)("Tip received (react off): " + tip.Summary));
				return;
			}
			_log.LogInfo((object)("Tip received: " + tip.Summary));
			VibeCommandCoordinator.RegisterTipActive(profile.Duration);
			TipIntegrationMode value = instance.Tips.Mode.Value;
			float num;
			switch (value)
			{
			case TipIntegrationMode.Passthrough:
				return;
			default:
				num = 1f;
				break;
			case TipIntegrationMode.Bonus:
				num = instance.Tips.BonusStrengthScale.Value;
				break;
			}
			float strengthScale = num;
			SendTipVibe(profile, strengthScale, value);
		}

		private TipVibeProfile BuildProfile(ParsedTipPayload parsed)
		{
			TipConfig tips = ModConfig.Instance.Tips;
			if (tips.UseConnectLevels.Value && (parsed.VibrateStrength > 0 || parsed.DurationSeconds > 0f))
			{
				int num = Mathf.Clamp(parsed.VibrateStrength, 0, 20);
				float duration = ((parsed.DurationSeconds > 0f) ? parsed.DurationSeconds : tips.Tier1Duration.Value);
				string preset = NormalizePreset(parsed.SpecialType);
				if (num <= 0)
				{
					num = ResolveTierStrength(parsed.Amount);
				}
				return new TipVibeProfile(parsed.TipperName, parsed.Amount, duration, num, preset);
			}
			return ResolveTierProfile(parsed.TipperName, parsed.Amount);
		}

		private TipVibeProfile ResolveTierProfile(string tipper, int amount)
		{
			TipConfig tips = ModConfig.Instance.Tips;
			if (amount >= tips.Tier3MinAmount.Value)
			{
				return new TipVibeProfile(tipper, amount, tips.Tier3Duration.Value, tips.Tier3Strength.Value, tips.Tier3Preset.Value);
			}
			if (amount >= tips.Tier2MinAmount.Value)
			{
				return new TipVibeProfile(tipper, amount, tips.Tier2Duration.Value, tips.Tier2Strength.Value, tips.Tier2Preset.Value);
			}
			return new TipVibeProfile(tipper, amount, tips.Tier1Duration.Value, tips.Tier1Strength.Value, tips.Tier1Preset.Value);
		}

		private int ResolveTierStrength(int amount)
		{
			return ResolveTierProfile("x", amount).Strength;
		}

		private void SendTipVibe(TipVibeProfile profile, float strengthScale, TipIntegrationMode mode)
		{
			int num = Mathf.Clamp(Mathf.RoundToInt((float)profile.Strength * strengthScale), 1, 20);
			float num2 = VibeRequest.NormalizeDuration(profile.Duration);
			string text = ((mode == TipIntegrationMode.Bonus) ? "TipBonus" : "TipMirror");
			VibeRequest request = (string.IsNullOrWhiteSpace(profile.Preset) ? new VibeRequest($"Vibrate:{num}", num2) : new VibeRequest(null, num2, profile.Preset));
			_log.LogInfo((object)$"Tip vibe: {text} {profile.Tipper} ({profile.Amount}) ({num2:0.###}s) -> {request.ToJson()}");
			_client.Send(request);
		}

		private static string NormalizePreset(string specialType)
		{
			if (string.IsNullOrWhiteSpace(specialType) || specialType.Equals("undefined", StringComparison.OrdinalIgnoreCase))
			{
				return "";
			}
			return specialType.Trim().ToLowerInvariant();
		}
	}
	internal readonly struct TipEvent
	{
		public readonly string TipperName;

		public readonly int Amount;

		public readonly float DurationSeconds;

		public readonly int Strength;

		public readonly string Preset;

		public readonly float ReceivedAt;

		public string Summary
		{
			get
			{
				if (!string.IsNullOrEmpty(Preset))
				{
					return $"{TipperName}: {Amount} tokens ({Preset}, {DurationSeconds:0.#}s)";
				}
				return $"{TipperName}: {Amount} tokens ({Strength}, {DurationSeconds:0.#}s)";
			}
		}

		public TipEvent(string tipperName, int amount, float durationSeconds, int strength, string preset, float receivedAt)
		{
			TipperName = tipperName ?? "Anonymous";
			Amount = amount;
			DurationSeconds = durationSeconds;
			Strength = strength;
			Preset = preset ?? "";
			ReceivedAt = receivedAt;
		}
	}
	internal static class TipFeed
	{
		private const int MaxEntries = 12;

		private static readonly List<TipEvent> Entries = new List<TipEvent>();

		private static float _lastBridgePingAt;

		private static bool _bridgeConnected;

		public static IReadOnlyList<TipEvent> RecentTips => Entries;

		public static bool BridgeConnected => _bridgeConnected;

		public static float LastBridgePingAt => _lastBridgePingAt;

		public static void RecordTip(TipEvent tip)
		{
			Entries.Insert(0, tip);
			while (Entries.Count > 12)
			{
				Entries.RemoveAt(Entries.Count - 1);
			}
		}

		public static void RecordBridgePing(float time)
		{
			_lastBridgePingAt = time;
			_bridgeConnected = true;
		}

		public static void MarkBridgeDisconnected()
		{
			_bridgeConnected = false;
		}
	}
	internal readonly struct ParsedTipPayload
	{
		public readonly bool Found;

		public readonly string TipperName;

		public readonly int Amount;

		public readonly float DurationSeconds;

		public readonly int VibrateStrength;

		public readonly string SpecialType;

		public ParsedTipPayload(bool found, string tipperName, int amount, float durationSeconds, int vibrateStrength, string specialType)
		{
			Found = found;
			TipperName = tipperName;
			Amount = amount;
			DurationSeconds = durationSeconds;
			VibrateStrength = vibrateStrength;
			SpecialType = specialType;
		}
	}
	internal static class TipJsonParser
	{
		public static ParsedTipPayload Parse(string json)
		{
			if (string.IsNullOrWhiteSpace(json))
			{
				return Empty();
			}
			int valueOrDefault = ExtractInt(json, "amount").GetValueOrDefault();
			string tipperName = ExtractString(json, "tipperName") ?? ExtractString(json, "tipper") ?? "Anonymous";
			float valueOrDefault2 = ExtractFloat(json, "time").GetValueOrDefault();
			int valueOrDefault3 = ExtractInt(json, "vibrate").GetValueOrDefault();
			string specialType = ExtractString(json, "specialType") ?? "";
			if (valueOrDefault <= 0 && valueOrDefault3 <= 0 && valueOrDefault2 <= 0f)
			{
				return Empty();
			}
			return new ParsedTipPayload(found: true, tipperName, valueOrDefault, valueOrDefault2, valueOrDefault3, specialType);
		}

		private static ParsedTipPayload Empty()
		{
			return new ParsedTipPayload(found: false, null, 0, 0f, 0, null);
		}

		private static string ExtractString(string json, string field)
		{
			string text = "\"" + field + "\":\"";
			int num = json.IndexOf(text, StringComparison.Ordinal);
			if (num < 0)
			{
				return null;
			}
			num += text.Length;
			int num2 = json.IndexOf('"', num);
			if (num2 >= 0)
			{
				return json.Substring(num, num2 - num);
			}
			return null;
		}

		private static int? ExtractInt(string json, string field)
		{
			string text = "\"" + field + "\":";
			int num = json.IndexOf(text, StringComparison.Ordinal);
			if (num < 0)
			{
				return null;
			}
			for (num += text.Length; num < json.Length && char.IsWhiteSpace(json[num]); num++)
			{
			}
			int i;
			for (i = num; i < json.Length && (char.IsDigit(json[i]) || json[i] == '-'); i++)
			{
			}
			if (!int.TryParse(json.Substring(num, i - num), out var result))
			{
				return null;
			}
			return result;
		}

		private static float? ExtractFloat(string json, string field)
		{
			string text = "\"" + field + "\":";
			int num = json.IndexOf(text, StringComparison.Ordinal);
			if (num < 0)
			{
				return null;
			}
			for (num += text.Length; num < json.Length && char.IsWhiteSpace(json[num]); num++)
			{
			}
			int i;
			for (i = num; i < json.Length && "0123456789.-".IndexOf(json[i]) >= 0; i++)
			{
			}
			if (!float.TryParse(json.Substring(num, i - num), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return null;
			}
			return result;
		}
	}
}
namespace VibingBattleGolf.Services
{
	internal sealed class LocalPlayerTracker : MonoBehaviour
	{
		private static readonly FieldRef<HoleProgressBarUi, int> DisplayedStrokes = AccessTools.FieldRefAccess<HoleProgressBarUi, int>("displayedStrokes");

		private PlayerGolfer _localGolfer;

		private bool _wasScored;

		private bool _wasEliminated;

		private int _lastHoleIndex = -1;

		private void Update()
		{
			if (!NetworkClient.active)
			{
				ResetState();
				return;
			}
			TrackHoleChanges();
			if (_localGolfer == null)
			{
				_localGolfer = FindLocalGolfer();
			}
			if (!((Object)(object)_localGolfer == (Object)null))
			{
				TrackScored();
				TrackEliminated();
			}
		}

		private void TrackHoleChanges()
		{
			int currentHoleCourseIndex = CourseManager.CurrentHoleCourseIndex;
			if (_lastHoleIndex != -1 && currentHoleCourseIndex != _lastHoleIndex)
			{
				_wasScored = false;
			}
			_lastHoleIndex = currentHoleCourseIndex;
		}

		private void TrackScored()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			bool flag = (int)_localGolfer.MatchResolution == 1 || ((Object)(object)_localGolfer.OwnBall != (Object)null && _localGolfer.OwnBall.isInHole);
			if (flag && !_wasScored)
			{
				if (TryTriggerHoleInOne())
				{
					_wasScored = true;
					return;
				}
				Plugin.Vibes.Trigger(VibeEvent.Scored);
			}
			_wasScored = flag;
		}

		private bool TryTriggerHoleInOne()
		{
			if (!ModConfig.Instance.HoleInOne.Enabled.Value)
			{
				return false;
			}
			if (GetDisplayedStrokes() != 1)
			{
				return false;
			}
			Plugin.Vibes.Trigger(VibeEvent.HoleInOne);
			return ModConfig.Instance.HoleInOneReplacesScored.Value;
		}

		private static int GetDisplayedStrokes()
		{
			HoleProgressBarUi instance = SingletonBehaviour<HoleProgressBarUi>.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				return DisplayedStrokes.Invoke(instance);
			}
			return -1;
		}

		private void TrackEliminated()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			bool flag = (int)_localGolfer.MatchResolution == 2;
			if (flag && !_wasEliminated)
			{
				try
				{
					EliminationReason localPlayerLatestImmediateEliminationReason = _localGolfer.LocalPlayerLatestImmediateEliminationReason;
					Plugin.OrbitalLaserVibes?.OnLocalPlayerWillBeEliminated(localPlayerLatestImmediateEliminationReason);
				}
				catch
				{
				}
				Plugin.Vibes.Trigger(VibeEvent.Eliminated);
			}
			_wasEliminated = flag;
		}

		private static PlayerGolfer FindLocalGolfer()
		{
			PlayerGolfer[] array = Object.FindObjectsByType<PlayerGolfer>((FindObjectsSortMode)0);
			foreach (PlayerGolfer val in array)
			{
				if ((Object)(object)val != (Object)null && ((NetworkBehaviour)val).isLocalPlayer)
				{
					return val;
				}
			}
			return null;
		}

		private void ResetState()
		{
			_localGolfer = null;
			_wasScored = false;
			_wasEliminated = false;
			_lastHoleIndex = -1;
		}
	}
	internal sealed class OrbitalLaserVibeController : MonoBehaviour
	{
		private enum Phase
		{
			Idle,
			Charging,
			MaxHit
		}

		private const float LostThreatGraceSeconds = 0.35f;

		private const float ProximityPadding = 2.5f;

		private const float SessionTailSeconds = 6f;

		private ManualLogSource _log;

		private LovenseClient _client;

		private Phase _phase;

		private int _activeLaserId;

		private Coroutine _rampCoroutine;

		private Coroutine _maxHitResetCoroutine;

		private float _lastThreatSeenAt = -999f;

		private float _maxHitUntil = -999f;

		private bool _sessionArmed;

		private float _sessionArmedUntil = -999f;

		private bool _wasLocalTargetThisSession;

		private bool _hadDamageContactThisSession;

		private bool _pollWasKnockedOut;

		private PlayerMatchResolution _pollLastResolution;

		private float _nextPollLogAt;

		private OrbitalLaserState _lastLoggedState = (OrbitalLaserState)(-1);

		private bool _lastLoggedTargeted;

		internal bool IsHandlingOrbitalHits
		{
			get
			{
				OrbitalLaserConfig orbitalLaser = ModConfig.Instance.OrbitalLaser;
				if (orbitalLaser != null && orbitalLaser.Enabled.Value)
				{
					return ModConfig.Instance.MasterEnabled.Value;
				}
				return false;
			}
		}

		private bool IsSessionArmed
		{
			get
			{
				if (_sessionArmed)
				{
					return Time.unscaledTime <= _sessionArmedUntil;
				}
				return false;
			}
		}

		internal void Initialize(ManualLogSource log, LovenseClient client)
		{
			_log = log;
			_client = client;
		}

		private void Update()
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			if (_phase == Phase.MaxHit && Time.unscaledTime >= _maxHitUntil)
			{
				FinishMaxHit("max duration elapsed");
			}
			if (!IsHandlingOrbitalHits)
			{
				if (_phase == Phase.Charging)
				{
					CancelCharge("feature disabled");
				}
				ClearSessionIfExpired();
			}
			else
			{
				if (_phase == Phase.MaxHit)
				{
					return;
				}
				if (IsSessionArmed)
				{
					PollForDestroyOutcome();
				}
				ClearSessionIfExpired();
				if (_phase == Phase.MaxHit)
				{
					return;
				}
				Vector3 position;
				Hittable hittable;
				if (!NetworkClient.active && !NetworkServer.active)
				{
					if (_phase == Phase.Charging)
					{
						CancelCharge("left match");
					}
					DisarmSession("left match");
				}
				else if (TryGetLocalPlayerPosition(out position, out hittable))
				{
					RefreshSessionFromAllLasers(position, hittable);
					bool wasTarget;
					OrbitalLaser val = FindThreateningLaser(position, hittable, out wasTarget);
					if ((Object)(object)val != (Object)null)
					{
						_lastThreatSeenAt = Time.unscaledTime;
						ExtendSession(((Object)val).GetInstanceID(), wasTarget);
						BeginCharge(((Object)val).GetInstanceID());
					}
					else if (_phase == Phase.Charging && Time.unscaledTime - _lastThreatSeenAt > 0.35f)
					{
						CancelCharge("anticipation ended (session still armed for KO/elim)");
					}
				}
			}
		}

		internal void OnLaserStateChanged(OrbitalLaser laser, OrbitalLaserState previous, OrbitalLaserState current)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Invalid comparison between Unknown and I4
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Invalid comparison between Unknown and I4
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			if (!IsHandlingOrbitalHits || (Object)(object)laser == (Object)null || _phase == Phase.MaxHit || !TryGetLocalPlayerPosition(out var position, out var hittable))
			{
				return;
			}
			bool flag = IsLocalPlayerTarget(laser, hittable);
			bool flag2 = IsThreateningLaser(laser, position, hittable);
			bool flag3 = HorizontalDistance(((Component)laser).transform.position, position) <= GetThreatRadius();
			if (flag2 || flag)
			{
				ExtendSession(((Object)laser).GetInstanceID(), flag);
			}
			if (flag2)
			{
				_lastThreatSeenAt = Time.unscaledTime;
				BeginCharge(((Object)laser).GetInstanceID());
			}
			if ((int)current == 3 || (int)current == 4 || (int)current == 0)
			{
				if (flag || flag3 || _wasLocalTargetThisSession || _hadDamageContactThisSession)
				{
					ExtendSession(((Object)laser).GetInstanceID(), flag || _wasLocalTargetThisSession, forceFullTail: true);
				}
				if (_phase == Phase.Charging)
				{
					CancelCharge($"laser {current} (session armed={IsSessionArmed})");
				}
			}
			LogStateOnce(laser, current, flag2 || flag || flag3);
		}

		internal void OnDamageHit()
		{
			if (!IsHandlingOrbitalHits)
			{
				return;
			}
			if (!VibeCommandCoordinator.CanProcessGameEvent())
			{
				_log.LogDebug((object)"Orbital laser hit vibe skipped: tip active.");
			}
			else if (_phase != Phase.MaxHit)
			{
				StopRampCoroutine();
				if (_phase == Phase.Charging)
				{
					_phase = Phase.Idle;
				}
				_hadDamageContactThisSession = true;
				ExtendSession(_activeLaserId, _wasLocalTargetThisSession, forceFullTail: true);
				TryStopOwnedGameVibe(12, "laser contact");
				if (!ModConfig.Instance.OrbitalLaser.CancelMaxIfNotDestroyed.Value)
				{
					StartMaxHit("hit (max-on-contact mode)");
					return;
				}
				_log.LogInfo((object)("Orbital laser contact — session armed; max on NEW KO/elimination " + $"(~{_sessionArmedUntil - Time.unscaledTime:0.0}s left)."));
				PollForDestroyOutcome();
			}
		}

		internal void OnLocalPlayerWillBeEliminated(EliminationReason reason)
		{
			//IL_0012: 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_0025: Unknown result type (might be due to invalid IL or missing references)
			if (IsHandlingOrbitalHits && _phase != Phase.MaxHit)
			{
				if (!IsOrbitalEliminationReason(reason))
				{
					_log.LogDebug((object)$"Elimination {reason} ignored for orbital max (not an orbital reason).");
				}
				else
				{
					StartMaxHit($"eliminated by {reason}");
				}
			}
		}

		internal void OnOrbitalLaserKnockout(KnockoutType knockoutType)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (IsHandlingOrbitalHits && _phase != Phase.MaxHit)
			{
				StartMaxHit($"knockout {knockoutType}");
			}
		}

		internal static bool IsOrbitalKnockoutType(KnockoutType knockoutType)
		{
			//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
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			if ((int)knockoutType != 17 && (int)knockoutType != 25 && (int)knockoutType != 32)
			{
				return (int)knockoutType == 35;
			}
			return true;
		}

		private void PollForDestroyOutcome()
		{
			//IL_0024: 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_002c: Invalid comparison between Unknown and I4
			//IL_005a: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Invalid comparison between Unknown and I4
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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)
			try
			{
				PlayerGolfer[] array = Object.FindObjectsByType<PlayerGolfer>((FindObjectsSortMode)0);
				foreach (PlayerGolfer val in array)
				{
					if ((Object)(object)val == (Object)null || !((NetworkBehaviour)val).isLocalPlayer)
					{
						continue;
					}
					PlayerMatchResolution matchResolution = val.MatchResolution;
					if ((int)matchResolution == 2 && (int)_pollLastResolution != 2)
					{
						_pollLastResolution = matchResolution;
						EliminationReason localPlayerLatestImmediateEliminationReason = val.LocalPlayerLatestImmediateEliminationReason;
						if (IsOrbitalEliminationReason(localPlayerLatestImmediateEliminationReason))
						{
							OnLocalPlayerWillBeEliminated(localPlayerLatestImmediateEliminationReason);
							break;
						}
					}
					else
					{
						_pollLastResolution = matchResolution;
					}
					PlayerMovement val2 = (((Object)(object)val.PlayerInfo != (Object)null) ? val.PlayerInfo.Movement : null);
					_pollWasKnockedOut = (Object)(object)val2 != (Object)null && val2.IsKnockedOut;
					break;
				}
			}
			catch
			{
			}
		}

		internal static bool IsOrbitalEliminationReason(EliminationReason reason)
		{
			//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
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			if ((int)reason != 24 && (int)reason != 25 && (int)reason != 40)
			{
				return (int)reason == 43;
			}
			return true;
		}

		private void StartMaxHit(string reason)
		{
			if (_phase != Phase.MaxHit)
			{
				StopRampCoroutine();
				StopMaxHitResetCoroutine();
				OrbitalLaserConfig orbitalLaser = ModConfig.Instance.OrbitalLaser;
				int strength = Mathf.Clamp(orbitalLaser.HitStrength.Value, 0, 20);
				float num = Mathf.Max(0.1f, orbitalLaser.HitDuration.Value);
				_phase = Phase.MaxHit;
				_maxHitUntil = Time.unscaledTime + num + 0.15f;
				ResetSessionFlags();
				TrySendGameVibe(strength, num, "OrbitalMax:" + reason);
				_maxHitResetCoroutine = ((MonoBehaviour)this).StartCoroutine(MaxHitResetRoutine(num + 0.15f));
			}
		}

		private bool TrySendGameVibe(int strength, float duration, string label)
		{
			if (!VibeCommandCoordinator.TryAcceptGameVibe(strength, duration, label, out var rejectReason))
			{
				_log.LogInfo((object)("Orbital vibe skipped (" + label + "): " + rejectReason));
				return false;
			}
			_log.LogInfo((object)$"Orbital vibe: {label} ({duration:0.###}s) strength={strength}");
			_client.Send(new VibeRequest($"Vibrate:{strength}", duration));
			return true;
		}

		private void TryStopOwnedGameVibe(int maxStrengthWeMayStop, string reason)
		{
			if (!VibeCommandCoordinator.TryAcceptGameStop(maxStrengthWeMayStop, out var rejectReason))
			{
				_log.LogDebug((object)("Orbital stop skipped (" + reason + "): " + rejectReason));
			}
			else
			{
				_client.SendStop();
			}
		}

		private IEnumerator MaxHitResetRoutine(float seconds)
		{
			yield return (object)new WaitForSecondsRealtime(Mathf.Max(0.1f, seconds));
			if (_phase == Phase.MaxHit)
			{
				FinishMaxHit("max reset coroutine");
			}
			_maxHitResetCoroutine = null;
		}

		private void FinishMaxHit(string reason)
		{
			if (_phase == Phase.MaxHit)
			{
				StopMaxHitResetCoroutine();
				_phase = Phase.Idle;
				_maxHitUntil = -999f;
				ResetSessionFlags();
				_log.LogInfo((object)("Orbital laser max phase cleared (" + reason + ") — ready for next laser."));
			}
		}

		private void StopMaxHitResetCoroutine()
		{
			if (_maxHitResetCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_maxHitResetCoroutine);
				_maxHitResetCoroutine = null;
			}
		}

		private void ExtendSession(int laserId, bool wasTarget, bool forceFullTail = false)
		{
			if (_phase != Phase.MaxHit)
			{
				if (laserId != 0)
				{
					_activeLaserId = laserId;
				}
				if (wasTarget)
				{
					_wasLocalTargetThisSession = true;
				}
				float num = Mathf.Max(6f, ModConfig.Instance.OrbitalLaser.DestroyGraceSeconds.Value);
				float num2 = Time.unscaledTime + num;
				if (forceFullTail || !_sessionArmed || num2 > _sessionArmedUntil)
				{
					_sessionArmedUntil = num2;
				}
				if (!_sessionArmed)
				{
					_sessionArmed = true;
					SnapshotPollBaselines();
					_log.LogInfo((object)($"Orbital laser session armed (target={_wasLocalTargetThisSession}, " + $"contact={_hadDamageContactThisSession}, {num:0.##}s tail)."));
				}
			}
		}

		private void SnapshotPollBaselines()
		{
			//IL_0072: 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_0027: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PlayerGolfer[] array = Object.FindObjectsByType<PlayerGolfer>((FindObjectsSortMode)0);
				foreach (PlayerGolfer val in array)
				{
					if (!((Object)(object)val == (Object)null) && ((NetworkBehaviour)val).isLocalPlayer)
					{
						_pollLastResolution = val.MatchResolution;
						PlayerMovement val2 = (((Object)(object)val.PlayerInfo != (Object)null) ? val.PlayerInfo.Movement : null);
						_pollWasKnockedOut = (Object)(object)val2 != (Object)null && val2.IsKnockedOut;
						break;
					}
				}
			}
			catch
			{
				_pollLastResolution = (PlayerMatchResolution)0;
				_pollWasKnockedOut = false;
			}
		}

		private void RefreshSessionFromAllLasers(Vector3 localPos, Hittable localHittable)
		{
			//IL_0024: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			OrbitalLaser[] array;
			try
			{
				array = Object.FindObjectsByType<OrbitalLaser>((FindObjectsSortMode)0);
			}
			catch
			{
				return;
			}
			OrbitalLaser[] array2 = array;
			foreach (OrbitalLaser val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				OrbitalLaserState networkstate;
				try
				{
					networkstate = val.Networkstate;
				}
				catch
				{
					continue;
				}
				if ((int)networkstate == 3 || (int)networkstate == 4)
				{
					bool flag = IsLocalPlayerTarget(val, localHittable);
					bool flag2 = HorizontalDistance(((Component)val).transform.position, localPos) <= GetThreatRadius();
					if (flag || flag2 || _wasLocalTargetThisSession)
					{
						ExtendSession(((Object)val).GetInstanceID(), flag || _wasLocalTargetThisSession);
					}
				}
			}
		}

		private void DisarmSession(string reason)
		{
			if (_sessionArmed)
			{
				_log.LogDebug((object)("Orbital laser session disarmed (" + reason + ")."));
			}
			ResetSessionFlags();
			if (_phase != Phase.MaxHit)
			{
				_phase = Phase.Idle;
			}
		}

		private void ClearSessionIfExpired()
		{
			if (_sessionArmed && Time.unscaledTime > _sessionArmedUntil)
			{
				_log.LogInfo((object)"Orbital laser session expired without KO/elim — max skipped.");
				ResetSessionFlags();
				if (_phase != Phase.MaxHit)
				{
					_phase = Phase.Idle;
				}
			}
		}

		private void ResetSessionFlags()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			_sessionArmed = false;
			_sessionArmedUntil = -999f;
			_wasLocalTargetThisSession = false;
			_hadDamageContactThisSession = false;
			_activeLaserId = 0;
			_pollWasKnockedOut = false;
			_pollLastResolution = (PlayerMatchResolution)0;
		}

		private OrbitalLaser FindThreateningLaser(Vector3 localPos, Hittable localHittable, out bool wasTarget)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			wasTarget = false;
			OrbitalLaser[] array;
			try
			{
				array = Object.FindObjectsByType<OrbitalLaser>((FindObjectsSortMode)0);
			}
			catch
			{
				return null;
			}
			OrbitalLaser result = null;
			float num = float.MaxValue;
			bool flag = false;
			OrbitalLaser[] array2 = array;
			foreach (OrbitalLaser val in array2)
			{
				if (!((Object)(object)val == (Object)null) && IsThreateningLaser(val, localPos, localHittable))
				{
					bool flag2 = IsLocalPlayerTarget(val, localHittable);
					float num2 = HorizontalDistance(((Component)val).transform.position, localPos);
					if (num2 < num)
					{
						num = num2;
						result = val;
						flag = flag2;
					}
				}
			}
			wasTarget = flag;
			return result;
		}

		private bool IsThreateningLaser(OrbitalLaser laser, Vector3 localPos, Hittable localHittable)
		{
			//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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//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_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Invalid comparison between Unknown and I4
			//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)
			OrbitalLaserState val;
			try
			{
				val = laser.Networkstate;
			}
			catch
			{
				try
				{
					val = laser.state;
				}
				catch
				{
					return false;
				}
			}
			if ((int)val != 1 && (int)val != 2)
			{
				return false;
			}
			if (IsLocalPlayerTarget(laser, localHittable))
			{
				return true;
			}
			return HorizontalDistance(((Component)laser).transform.position, localPos) <= GetThreatRadius();
		}

		private static bool IsLocalPlayerTarget(OrbitalLaser laser, Hittable localHittable)
		{
			try
			{
				Hittable val = laser.Networktarget;
				if ((Object)(object)val == (Object)null)
				{
					val = laser.target;
				}
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				if ((Object)(object)localHittable != (Object)null && (Object)(object)val == (Object)(object)localHittable)
				{
					return true;
				}
				Entity asEntity = val.AsEntity;
				if ((Object)(object)asEntity == (Object)null || !asEntity.IsPlayer)
				{
					return false;
				}
				PlayerInfo playerInfo = asEntity.PlayerInfo;
				return (Object)(object)playerInfo != (Object)null && ((NetworkBehaviour)playerInfo).isLocalPlayer;
			}
			catch
			{
				return false;
			}
		}

		private float GetThreatRadius()
		{
			try
			{
				ItemSettings itemSettings = GameManager.ItemSettings;
				if ((Object)(object)itemSettings != (Object)null)
				{
					float orbitalLaserExplosionMaxRange = itemSettings.OrbitalLaserExplosionMaxRange;
					if (orbitalLaserExplosionMaxRange > 0.5f)
					{
						return orbitalLaserExplosionMaxRange + 2.5f;
					}
				}
			}
			catch
			{
			}
			return 10.5f;
		}

		private static float HorizontalDistance(Vector3 a, Vector3 b)
		{
			//IL_0018: 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)
			a.y = 0f;
			b.y = 0f;
			return Vector3.Distance(a, b);
		}

		private static bool TryGetLocalPlayerPosition(out Vector3 position, out Hittable hittable)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: 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)
			position = default(Vector3);
			hittable = null;
			try
			{
				PlayerInfo[] array = Object.FindObjectsByType<PlayerInfo>((FindObjectsSortMode)0);
				foreach (PlayerInfo val in array)
				{
					if (!((Object)(object)val == (Object)null) && ((NetworkBehaviour)val).isLocalPlayer)
					{
						hittable = val.AsHittable;
						position = ((Component)val).transform.position;
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private void BeginCharge(int laserId)
		{
			if (VibeCommandCoordinator.CanProcessGameEvent() && _phase != Phase.MaxHit && (_phase != Phase.Charging || _activeLaserId != laserId))
			{
				StopRampCoroutine();
				_activeLaserId = laserId;
				_phase = Phase.Charging;
				_lastThreatSeenAt = Time.unscaledTime;
				float num = ResolveChargeDuration();
				_log.LogInfo((object)$"Orbital laser charge ramp start ({num:0.###}s).");
				_rampCoroutine = ((MonoBehaviour)this).StartCoroutine(ChargeRampRoutine(num));
			}
		}

		private void CancelCharge(string reason)
		{
			if (_phase == Phase.Charging)
			{
				_log.LogInfo((object)("Orbital laser charge cancelled (" + reason + ")."));
				StopRampCoroutine();
				int maxStrengthWeMayStop = Mathf.Clamp(ModConfig.Instance.OrbitalLaser.ChargeEndStrength.Value, 0, 20);
				TryStopOwnedGameVibe(maxStrengthWeMayStop, reason);
				_phase = Phase.Idle;
			}
		}

		private IEnumerator ChargeRampRoutine(float durationSeconds)
		{
			OrbitalLaserConfig orbitalLaser = ModConfig.Instance.OrbitalLaser;
			int start = Mathf.Clamp(orbitalLaser.ChargeStartStrength.Value, 0, 20);
			int end = Mathf.Clamp(orbitalLaser.ChargeEndStrength.Value, 0, 20);
			if (end < start)
			{
				end = start;
			}
			float duration = Mathf.Max(0.25f, durationSeconds);
			float elapsed = 0f;
			float step = 0.2f;
			while (_phase == Phase.Charging && elapsed < duration)
			{
				float num = Mathf.Clamp01(elapsed / duration);
				int num2 = Mathf.RoundToInt(Mathf.Lerp((float)start, (float)end, num));
				TrySendGameVibe(num2, 1.5f, $"OrbitalCharge:{num2}");
				yield return (object)new WaitForSecondsRealtime(step);
				elapsed += step;
			}
			if (_phase == Phase.Charging)
			{
				TrySendGameVibe(end, 2f, $"OrbitalChargeHold:{end}");
				while (_phase == Phase.Charging)
				{
					yield return (object)new WaitForSecondsRealtime(0.5f);
					if (_phase == Phase.Charging)
					{
						TrySendGameVibe(end, 1.5f, $"OrbitalChargeHold:{end}");
					}
				}
			}
			_rampCoroutine = null;
		}

		private float ResolveChargeDuration()
		{
			float value = ModConfig.Instance.OrbitalLaser.ChargeDuration.Value;
			try
			{
				ItemSettings itemSettings = GameManager.ItemSettings;
				if ((Object)(object)itemSettings != (Object)null)
				{
					float orbitalLaserAnticipationDuration = itemSettings.OrbitalLaserAnticipationDuration;
					float orbitalLaserActivationTotalDuration = itemSettings.OrbitalLaserActivationTotalDuration;
					float num = Mathf.Max(orbitalLaserAnticipationDuration, orbitalLaserActivationTotalDuration);
					if (num > 0.25f)
					{
						return num;
					}
				}
			}
			catch
			{
			}
			return Mathf.Max(0.5f, value);
		}

		private void LogStateOnce(OrbitalLaser laser, OrbitalLaserState state, bool relevant)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: 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_002c: 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_0020: 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)
			if ((state != _lastLoggedState || relevant != _lastLoggedTargeted) && (!(Time.unscaledTime < _nextPollLogAt) || state != _lastLoggedState))
			{
				_lastLoggedState = state;
				_lastLoggedTargeted = relevant;
				_nextPollLogAt = Time.unscaledTime + 0.5f;
				_log.LogInfo((object)($"Orbital laser state={state}, relevant={relevant}, phase={_phase}, " + $"session={IsSessionArmed}, target={_wasLocalTargetThisSession}, contact={_hadDamageContactThisSession}"));
			}
		}

		private void StopRampCoroutine()
		{
			if (_rampCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_rampCoroutine);
				_rampCoroutine = null;
			}
		}

		private void OnDestroy()
		{
			StopRampCoroutine();
			StopMaxHitResetCoroutine();
			ResetSessionFlags();
			_phase = Phase.Idle;
		}
	}
	internal sealed class TestHotkeyComponent : MonoBehaviour
	{
		private void Update()
		{
			//IL_0014: 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_001b: 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)
			Keyboard current = Keyboard.current;
			if (current != null)
			{
				Key value = ModConfig.Instance.TestVibeKey.Value;
				if (((ButtonControl)current[value]).wasPressedThisFrame)
				{
					Plugin.Log.LogInfo((object)$"Test vibe hotkey pressed ({value}). Retrying Lovense connection if needed.");
					Plugin.Vibes.Trigger(VibeEvent.TestPing);
				}
			}
		}
	}
	internal static class VibeCommandCoordinator
	{
		private static float _tipActiveUntil;

		private static float _gameActiveUntil;

		private static int _gameActiveStrength;

		private static string _gameActiveLabel = "";

		public static bool IsTipBlockingGameEvents
		{
			get
			{
				if (ModConfig.Instance.Tips.ReactToTips.Value && ModConfig.Instance.Tips.BlockGameDuringTips.Value)
				{
					return Time.unscaledTime < _tipActiveUntil;
				}
				return false;
			}
		}

		public static bool CanProcessGameEvent()
		{
			return !IsTipBlockingGameEvents;
		}

		public static void RegisterTipActive(float durationSeconds)
		{
			if (!(durationSeconds <= 0f))
			{
				float num = Time.unscaledTime + durationSeconds;
				if (num > _tipActiveUntil)
				{
					_tipActiveUntil = num;
				}
				ClearGameVibeTracking();
			}
		}

		public static bool TryAcceptGameVibe(int strength, float durationSeconds, string label, out string rejectReason)
		{
			rejectReason = null;
			if (!CanProcessGameEvent())
			{
				rejectReason = "tip active";
				return false;
			}
			strength = Mathf.Clamp(strength, 0, 20);
			durationSeconds = Mathf.Max(0.1f, durationSeconds);
			float unscaledTime = Time.unscaledTime;
			float num = unscaledTime + durationSeconds;
			if (unscaledTime < _gameActiveUntil)
			{
				if (strength < _gameActiveStrength)
				{
					rejectReason = "weaker than active '" + _gameActiveLabel + "' " + $"(new {strength} < active {_gameActiveStrength}, " + $"{_gameActiveUntil - unscaledTime:0.0}s left)";
					return false;
				}
				if (strength == _gameActiveStrength && num <= _gameActiveUntil + 0.05f)
				{
					rejectReason = "does not outrank active '" + _gameActiveLabel + "' " + $"(same strength {strength}, shorter/equal remaining)";
					return false;
				}
			}
			_gameActiveUntil = num;
			_gameActiveStrength = strength;
			_gameActiveLabel = label ?? "";
			return true;
		}

		public static bool TryAcceptGameStop(int maxStrengthWeMayStop, out string rejectReason)
		{
			rejectReason = null;
			if (IsTipBlockingGameEvents)
			{
				rejectReason = "tip active";
				return false;
			}
			if (Time.unscaledTime >= _gameActiveUntil)
			{
				ClearGameVibeTracking();
				return true;
			}
			if (_gameActiveStrength > maxStrengthWeMayStop)
			{
				rejectReason = "stronger vibe '" + _gameActiveLabel + "' still active " + $"({_gameActiveStrength} > {maxStrengthWeMayStop})";
				return false;
			}
			ClearGameVibeTracking();
			return true;
		}

		public static void ClearGameVibeTracking()
		{
			_gameActiveUntil = 0f;
			_gameActiveStrength = 0;
			_gameActiveLabel = "";
		}
	}
	internal enum VibeEvent
	{
		ItemHit,
		BallHit,
		SwingProjectileHit,
		DiveHit,
		HornBlast,
		Knockout,
		Scored,
		HoleInOne,
		ItemUsed,
		Eliminated,
		EnemyKill,
		TestPing
	}
	internal readonly struct VibeTriggerContext
	{
		public readonly ItemType? ItemType;

		public readonly float? Distance;

		public readonly float? Power;

		public VibeTriggerContext(ItemType? itemType = null, float? distance = null, float? power = null)
		{
			ItemType = itemType;
			Distance = distance;
			Power = power;
		}
	}
	internal sealed class VibeController
	{
		private readonly ManualLogSource _log;

		private readonly LovenseClient _client;

		private float _lastHornBlastAt = -999f;

		public VibeController(ManualLogSource log, LovenseClient client)
		{
			_log = log;
			_client = client;
		}

		public void Preview(int strength, float duration, string preset, string label)
		{
			SendValues(strength, duration, preset, 1f, "Preview:" + label, force: true);
		}

		public void Preview(EventConfig eventConfig, string label)
		{
			if (eventConfig != null)
			{
				Preview(eventConfig.Strength.Value, eventConfig.Duration.Value, eventConfig.Preset.Value, label);
			}
		}

		public void Trigger(VibeEvent vibeEvent, VibeTriggerContext context = default(VibeTriggerContext))
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Invalid comparison between Unknown and I4
			if (!ModConfig.Instance.MasterEnabled.Value)
			{
				return;
			}
			if (!VibeCommandCoordinator.CanProcessGameEvent())
			{
				_log.LogDebug((object)$"Game vibe skipped ({vibeEvent}): tip active.");
				return;
			}
			if (vibeEvent == VibeEvent.HornBlast)
			{
				if (Time.unscaledTime - _lastHornBlastAt < 0.75f)
				{
					_log.LogDebug((object)"Horn blast vibe debounced.");
					return;
				}
				_lastHornBlastAt = Time.unscaledTime;
			}
			if (vibeEvent == VibeEvent.ItemHit && (int)context.ItemType.GetValueOrDefault() == 10 && ModConfig.Instance.OrbitalLaser.Enabled.Value)
			{
				_log.LogDebug((object)"Skipping generic OrbitalLaser ItemHit — handled by orbital sequence.");
			}
			else if (!TryTriggerItemSpecific(vibeEvent, context))
			{
				EventConfig eventConfig = GetEventConfig(vibeEvent);
				if (eventConfig != null && eventConfig.Enabled.Value)
				{
					float scale = ComputeScale(vibeEvent, eventConfig, context);
					Send(eventConfig, scale, vibeEvent.ToString());
				}
			}
		}

		private bool TryTriggerItemSpecific(VibeEvent vibeEvent, VibeTriggerContext context)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (!context.ItemType.HasValue || (int)context.ItemType.Value == 0)
			{
				return false;
			}
			if (!ModConfig.Instance.Items.TryGetValue(context.ItemType.Value, out var value))
			{
				return false;
			}
			switch (vibeEvent)
			{
			case VibeEvent.ItemHit:
				if (value.HitEnabled.Value)
				{
					SendItemProfile(value.HitStrength.Value, value.HitDuration.Value, value.HitPreset.Value, ComputeDistanceScale(ModConfig.Instance.ItemHitScaling, context.Distance), $"ItemHit:{context.ItemType}");
					return true;
				}
				break;
			case VibeEvent.ItemUsed:
				if (value.UseEnabled.Value)
				{
					SendItemProfile(value.UseStrength.Value, value.UseDuration.Value, value.UsePreset.Value, 1f, $"ItemUsed:{context.ItemType}");
					return true;
				}
				break;
			}
			return false;
		}

		private EventConfig GetEventConfig(VibeEvent vibeEvent)
		{
			ModConfig instance = ModConfig.Instance;
			return vibeEvent switch
			{
				VibeEvent.ItemHit => instance.ItemHit, 
				VibeEvent.BallHit => instance.BallHit, 
				VibeEvent.SwingProjectileHit => instance.SwingProjectileHit, 
				VibeEvent.DiveHit => instance.DiveHit, 
				VibeEvent.HornBlast => instance.HornBlast, 
				VibeEvent.Knockout => instance.Knockout, 
				VibeEvent.Scored => instance.Scored, 
				VibeEvent.HoleInOne => instance.HoleInOne, 
				VibeEvent.ItemUsed => instance.ItemUsed, 
				VibeEvent.Eliminated => instance.Eliminated, 
				VibeEvent.EnemyKill => instance.EnemyKill, 
				VibeEvent.TestPing => instance.TestPing, 
				_ => null, 
			};
		}

		private float ComputeScale(VibeEvent vibeEvent, EventConfig eventConfig, VibeTriggerContext context)
		{
			float num = 1f;
			if (vibeEvent == VibeEvent.ItemHit && context.Distance.HasValue)
			{
				num *= ComputeDistanceScale(ModConfig.Instance.ItemHitScaling, context.Distance.Value);
			}
			if (vibeEvent == VibeEvent.Knockout && context.Distance.HasValue)
			{
				num *= ComputeDistanceScale(ModConfig.Instance.KnockoutScaling, context.Distance.Value);
			}
			if (eventConfig.ScaleWithPower.Value && context.Power.HasValue)
			{
				float num2 = Mathf.Clamp01(context.Power.Value);
				num *= Mathf.Lerp(eventConfig.MinPowerScale.Value, eventConfig.MaxPowerScale.Value, num2);
			}
			return num;
		}

		private static float ComputeDistanceScale(DistanceScalingConfig scaling, float? distance)
		{
			if (!distance.HasValue || !scaling.Enabled.Value)
			{
				return 1f;
			}
			float num = Mathf.Max(scaling.MinDistance.Value, scaling.MaxDistance.Value);
			float num2 = Mathf.Min(scaling.MinDistance.Value, scaling.MaxDistance.Value);
			float num3 = Mathf.InverseLerp(num, num2, distance.Value);
			return Mathf.Lerp(scaling.MinScale.Value, scaling.MaxScale.Value, num3);
		}

		private void SendItemProfile(int strength, float duration, string preset, float scale, string label)
		{
			SendValues(strength, duration, preset, scale, label);
		}

		private void Send(EventConfig eventConfig, float scale, string label)
		{
			SendValues(eventConfig.Strength.Value, eventConfig.Duration.Value, eventConfig.Preset.Value, scale, label);
		}

		private void SendValues(int strength, float duration, string preset, float scale, string label, bool force = false)
		{
			float num = VibeRequest.NormalizeDuration(duration);
			int num2 = ClampStrength((float)strength * scale);
			string rejectReason;
			if (force)
			{
				VibeCommandCoordinator.ClearGameVibeTracking();
			}
			else if (!VibeCommandCoordinator.TryAcceptGameVibe(num2, num, label, out rejectReason))
			{
				_log.LogInfo((object)("Vibe skipped (" + label + "): " + rejectReason));
				return;
			}
			if (force)
			{
				VibeCommandCoordinator.TryAcceptGameVibe(num2, num, label, out var _);
			}
			VibeRequest request = (string.IsNullOrWhiteSpace(preset) ? new VibeRequest($"Vibrate:{num2}", num) : new VibeRequest(null, num, preset.Trim()));
			_log.LogInfo((object)$"Vibe trigger: {label} ({num:0.###}s) -> {request.ToJson()}");
			_client.Send(request);
		}

		private static int ClampStrength(float value)
		{
			return Mathf.Clamp(Mathf.RoundToInt(value), 0, 20);
		}
	}
}
namespace VibingBattleGolf.Patches
{
	[HarmonyPatch(typeof(OrbitalLaser))]
	internal static class OrbitalLaserPatches
	{
		[HarmonyPatch("OnStateChanged")]
		[HarmonyPostfix]
		private static void OnStateChanged_Postfix(OrbitalLaser __instance, OrbitalLaserState previousState, OrbitalLaserState currentState)
		{
			//IL_001d: 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)
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)Plugin.OrbitalLaserVibes == (Object)null))
			{
				Plugin.OrbitalLaserVibes.OnLaserStateChanged(__instance, previousState, currentState);
			}
		}

		[HarmonyPatch("OnStartClient")]
		[HarmonyPostfix]
		private static void OnStartClient_Postfix(OrbitalLaser __instance)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || (Object)(object)Plugin.OrbitalLaserVibes == (Object)null)
			{
				return;
			}
			try
			{
				Plugin.OrbitalLaserVibes.OnLaserStateChanged(__instance, (OrbitalLaserState)0, __instance.Networkstate);
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(PlayerGolfer))]
	internal static class PlayerGolferPatches
	{
		[HarmonyPatch("OnLocalPlayerWillBeEliminated")]
		[HarmonyPostfix]
		private static void OnLocalPlayerWillBeEliminated_Postfix(PlayerGolfer __instance, EliminationReason immediateEliminationReason, Vector3 eliminationPosition)
		{
			//IL_001c: 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)
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Plugin.Log.LogInfo((object)$"Local player will be eliminated: {immediateEliminationReason}");
				Plugin.OrbitalLaserVibes?.OnLocalPlayerWillBeEliminated(immediateEliminationReason);
			}
		}

		[HarmonyPatch("UserCode_RpcInformWillBeEliminated__EliminationReason__Vector3")]
		[HarmonyPostfix]
		private static void UserCode_RpcInformWillBeEliminated_Postfix(PlayerGolfer __instance)
		{
			//IL_0014: 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_0025: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || !((NetworkBehaviour)__instance).isLocalPlayer)
			{
				return;
			}
			try
			{
				EliminationReason localPlayerLatestImmediateEliminationReason = __instance.LocalPlayerLatestImmediateEliminationReason;
				Plugin.OrbitalLaserVibes?.OnLocalPlayerWillBeEliminated(localPlayerLatestImmediateEliminationReason);
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(PlayerInfo))]
	internal static class PlayerInfoPatches
	{
		[HarmonyPatch("UserCode_RpcInformOfBlownAirhorn__PlayerInventory")]
		[HarmonyPostfix]
		private static void UserCode_RpcInformOfBlownAirhorn_Postfix(PlayerInfo __instance)
		{
			TriggerHornBlastIfLocal(__instance, "RpcInformOfBlownAirhorn");
		}

		[HarmonyPatch("PlayAirhornReactionVfxInternal")]
		[HarmonyPostfix]
		private static void PlayAirhornReactionVfxInternal_Postfix(PlayerInfo __instance)
		{
			TriggerHornBlastIfLocal(__instance, "PlayAirhornReactionVfxInternal");
		}

		[HarmonyPatch("UserCode_RpcInformOfFlashCameraHit__Single")]
		[HarmonyPostfix]
		private static void UserCode_RpcInformOfFlashCameraHit_Postfix(PlayerInfo __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Plugin.Log.LogInfo((object)"Flash camera hit detected.");
				Plugin.Vibes.Trigger(VibeEvent.ItemHit, new VibeTriggerContext((ItemType)14));
			}
		}

		private static void TriggerHornBlastIfLocal(PlayerInfo instance, string source)
		{
			if (!((Object)(object)instance == (Object)null) && ((NetworkBehaviour)instance).isLocalPlayer)
			{
				Plugin.Log.LogInfo((object)("Horn blast detected (" + source + ")."));
				Plugin.Vibes.Trigger(VibeEvent.HornBlast);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerInventory))]
	internal static class PlayerInventoryPatches
	{
		private static ItemType _lastUsedItem;

		[HarmonyPatch("TryUseItem")]
		[HarmonyPrefix]
		private static void TryUseItem_Prefix(PlayerInventory __instance, bool isAirhornReaction)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || !((NetworkBehaviour)__instance).isLocalPlayer || isAirhornReaction)
			{
				_lastUsedItem = (ItemType)0;
			}
			else
			{
				_lastUsedItem = __instance.GetEffectivelyEquippedItem(false);
			}
		}

		[HarmonyPatch("TryUseItem")]
		[HarmonyPostfix]
		private static void TryUseItem_Postfix(PlayerInventory __instance, bool isAirhornReaction, bool __result)
		{
			//IL_001a: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (!(!__result || isAirhornReaction) && !((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				ItemType value = (((int)_lastUsedItem != 0) ? _lastUsedItem : __instance.GetEffectivelyEquippedItem(false));
				Plugin.Vibes.Trigger(VibeEvent.ItemUsed, new VibeTriggerContext(value));
			}
		}
	}
	[HarmonyPatch(typeof(PlayerMovement))]
	internal static class PlayerMovementPatches
	{
		[HarmonyPatch("OnLocalPlayerWillApplyItemHitPhysics")]
		[HarmonyPostfix]
		private static void OnLocalPlayerWillApplyItemHitPhysics_Postfix(PlayerMovement __instance, ItemType itemType, float distance, Vector3 incomingVelocityChange)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_003b: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				if ((int)itemType == 10 && (Object)(object)Plugin.OrbitalLaserVibes != (Object)null && Plugin.OrbitalLaserVibes.IsHandlingOrbitalHits)
				{
					Plugin.OrbitalLaserVibes.OnDamageHit();
					return;
				}
				Vector3 val = incomingVelocityChange;
				float value = ((Vector3)(ref val)).magnitude / 20f;
				Plugin.Vibes.Trigger(VibeEvent.ItemHit, new VibeTriggerContext(itemType, distance, value));
			}
		}

		[HarmonyPatch("OnLocalPlayerWillApplyGolfSwingHitPhysics")]
		[HarmonyPostfix]
		private static void OnLocalPlayerWillApplyGolfSwingHitPhysics_Postfix(PlayerMovement __instance, float power, Vector3 incomingVelocityChange)
		{
			//IL_0012: 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)
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Vector3 val = incomingVelocityChange;
				float num = ((Vector3)(ref val)).magnitude / 25f;
				float value = Mathf.Clamp01(Mathf.Max(power, num));
				VibeController vibes = Plugin.Vibes;
				float? power2 = value;
				vibes.Trigger(VibeEvent.BallHit, new VibeTriggerContext(null, null, power2));
			}
		}

		[HarmonyPatch("OnLocalPlayerWillApplySwingProjectileHitPhysics")]
		[HarmonyPostfix]
		private static void OnLocalPlayerWillApplySwingProjectileHitPhysics_Postfix(PlayerMovement __instance, Vector3 incomingVelocityChange)
		{
			//IL_0012: 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)
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Vector3 val = incomingVelocityChange;
				float value = Mathf.Clamp01(((Vector3)(ref val)).magnitude / 20f);
				VibeController vibes = Plugin.Vibes;
				float? power = value;
				vibes.Trigger(VibeEvent.SwingProjectileHit, new VibeTriggerContext(null, null, power));
			}
		}

		[HarmonyPatch("OnLocalPlayerWillApplyDiveHitPhysics")]
		[HarmonyPostfix]
		private static void OnLocalPlayerWillApplyDiveHitPhysics_Postfix(PlayerMovement __instance, Vector3 appliedVelocity)
		{
			//IL_0012: 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)
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Vector3 val = appliedVelocity;
				float value = Mathf.Clamp01(((Vector3)(ref val)).magnitude / 15f);
				VibeController vibes = Plugin.Vibes;
				float? power = value;
				vibes.Trigger(VibeEvent.DiveHit, new VibeTriggerContext(null, null, power));
			}
		}

		[HarmonyPatch("TryKnockOut")]
		[HarmonyPostfix]
		private static void TryKnockOut_Postfix(PlayerMovement __instance, KnockoutType knockoutType, ref bool isNewKnockout, float distance)
		{
			//IL_0016: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			if (isNewKnockout && !((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				if (OrbitalLaserVibeController.IsOrbitalKnockoutType(knockoutType))
				{
					Plugin.Log.LogInfo((object)$"Local player knocked out by {knockoutType}.");
					Plugin.OrbitalLaserVibes?.OnOrbitalLaserKnockout(knockoutType);
				}
				VibeController vibes = Plugin.Vibes;
				float? distance2 = distance;
				vibes.Trigger(VibeEvent.Knockout, new VibeTriggerContext(null, distance2));
			}
		}

		[HarmonyPatch("RpcInformKnockedOutOtherPlayer")]
		[HarmonyPostfix]
		private static void RpcInformKnockedOutOtherPlayer_Postfix(PlayerMovement __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Plugin.Vibes.Trigger(VibeEvent.EnemyKill);
			}
		}

		[HarmonyPatch("UserCode_RpcInformKnockedOutOtherPlayer__PlayerInfo__Boolean")]
		[HarmonyPostfix]
		private static void UserCode_RpcInformKnockedOutOtherPlayer_Postfix(PlayerMovement __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Plugin.Vibes.Trigger(VibeEvent.EnemyKill);
			}
		}

		[HarmonyPatch("InformReactedToAirhorn")]
		[HarmonyPostfix]
		private static void InformReactedToAirhorn_Postfix(PlayerMovement __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).isLocalPlayer)
			{
				Plugin.Log.LogInfo((object)"Horn blast detected (InformReactedToAirhorn).");
				Plugin.Vibes.Trigger(VibeEvent.HornBlast);
			}
		}
	}
}
namespace VibingBattleGolf.Lovense
{
	internal sealed class LovenseClient : IDisposable
	{
		private static readonly string[] DefaultEndpoints = new string[5] { "http://127.0.0.1:20010", "https://127-0-0-1.lovense.club:30010", "http://127.0.0.1:30010", "http://127.0.0.1:34567", "https://127-0-0-1.lovense.club:34568" };

		private readonly ManualLogSource _log;

		private readonly string _configuredBaseUrl;

		private readonly bool _autoDiscover;

		private readonly LovenseRequestQueue _queue;

		private VibeActivationTimer _timer;

		private string _activeBaseUrl;

		private bool _discoveryInProgress;

		public string PlatformName { get; }

		public LovenseClient(ManualLogSource log, string baseUrl, string platformName, bool autoDiscover, LovenseRequestQueue queue)
		{
			_log = log;
			_configuredBaseUrl = (string.IsNullOrWhiteSpace(baseUrl) ? null : baseUrl.Trim().TrimEnd('/'));
			PlatformName = platformName;
			_autoDiscover = autoDiscover;
			_queue = queue;
			_activeBaseUrl = ((_autoDiscover || string.IsNullOrWhiteSpace(_configuredBaseUrl)) ? null : _configuredBaseUrl);
		}

		public void SetTimer(VibeActivationTimer timer)
		{
			_timer = timer;
		}

		public void Send(VibeRequest request)
		{
			if (string.IsNullOrWhiteSpace(_activeBaseUrl))
			{
				DiscoverAsync().ContinueWith(delegate(Task<bool> discovered)
				{
					if (discovered.Result)
					{
						Dispatch(request);
					}
				});
			}
			else
			{
				Dispatch(request);
			}
		}

		public void SendStop()
		{
			Dispatch(VibeRequest.Stop);
		}

		private void Dispatch(VibeRequest request)
		{
			QueueCommand(request.ToJson());
			if (!request.IsStop && !((Object)(object)_timer == (Object)null))
			{
				_timer.ScheduleStop(request.DurationSeconds);
			}
		}

		public Task<bool> DiscoverAsync()
		{
			TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
			if (_discoveryInProgress)
			{
				if (!string.IsNullOrWhiteSpace(_activeBaseUrl))
				{
					taskCompletionSource.SetResult(result: true);
					return taskCompletionSource.Task;
				}
				taskCompletionSource.SetResult(result: false);
				return taskCompletionSource.Task;
			}
			_discoveryInProgress = true;
			TryNextEndpoint(GetCandidateEndpoints().GetEnumerator(), taskCompletionSource);
			return taskCompletionSource.Task;
		}

		private void TryNextEndpoint(IEnumerator<string> endpoints, TaskCompletionSource<bool> tcs)
		{
			if (!endpoints.MoveNext())
			{
				_discoveryInProgress = false;
				_log.LogWarning((object)"Lovense not reachable. Open Lovense Connect, pair your toy, keep it running, then press F9 to retry.");
				tcs.SetResult(result: false);
				return;
			}
			string endpoint = endpoints.Current;
			_queue.Enqueue(endpoint, "{\"command\":\"GetToys\"}", _log, delegate(LovenseResult result)
			{
				if (result.Success)
				{
					_activeBaseUrl = endpoint;
					_discoveryInProgress = false;
					_log.LogInfo((object)("Lovense connected via " + endpoint + "."));
					if (!string.IsNullOrWhiteSpace(result.ToySummary))
					{
						_log.LogInfo((object)("Lovense toys: " + result.ToySummary));
					}
					tcs.TrySetResult(result: true);
				}
				else
				{
					_log.LogDebug((object)("Lovense probe failed for " + endpoint + ": " + result.Error));
					TryNextEndpoint(endpoints, tcs);
				}
			});
		}

		private void QueueCommand(string json)
		{
			if (string.IsNullOrWhiteSpace(_activeBaseUrl))
			{
				_log.LogWarning((object)"Lovense command skipped because no endpoint is connected.");
				return;
			}
			_queue.Enqueue(_activeBaseUrl, json, _log, delegate(LovenseResult result)
			{
				if (!result.Success)
				{
					_log.LogWarning((object)("Lovense command failed on " + _activeBaseUrl + ": " + result.Error));
					_activeBaseUrl = null;
				}
			});
		}

		private IEnumerable<string> GetCandidateEndpoints()
		{
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (!string.IsNullOrWhiteSpace(_configuredBaseUrl) && seen.Add(_configuredBaseUrl))
			{
				yield return _configuredBaseUrl;
			}
			if (!_autoDiscover)
			{
				yield break;
			}
			string[] defaultEndpoints = DefaultEndpoints;
			foreach (string text in defaultEndpoints)
			{
				if (seen.Add(text))
				{
					yield return text;
				}
			}
		}

		public void Dispose()
		{
		}
	}
	internal readonly struct VibeRequest
	{
		private const float LovenseMinimumTimedSeconds = 1f;

		public readonly string Action;

		public readonly float DurationSeconds;

		public readonly string PresetName;

		public bool IsStop => string.Equals(Action, "Stop", StringComparison.Ordinal);

		public static VibeRequest Stop => new VibeRequest("Stop", 0f);

		public VibeRequest(string action, float durationSeconds, string presetName = null)
		{
			Action = action;
			DurationSeconds = NormalizeDuration(durationSeconds);
			PresetName = presetName;
		}

		public string ToJson()
		{
			if (IsStop)
			{
				return "{\"command\":\"Function\",\"action\":\"Stop\",\"timeSec\":0,\"apiVer\":1}";
			}
			string text = FormatApiDuration(DurationSeconds);
			if (!string.IsNullOrEmpty(PresetName))
			{
				return "{\"command\":\"Preset\",\"name\":\"" + PresetName + "\",\"timeSec\":" + text + ",\"apiVer\":1}";
			}
			return "{\"command\":\"Function\",\"action\":\"" + Action + "\",\"timeSec\":" + text + ",\"stopPrevious\":1,\"apiVer\":1}";
		}

		public static float NormalizeDuration(float seconds)
		{
			if (seconds <= 0f)
			{
				return 1f;
			}
			return seconds;
		}

		private static string FormatApiDuration(float seconds)
		{
			return Mathf.Max(1f, seconds).ToString("0.###", CultureInfo.InvariantCulture);
		}
	}
	internal readonly struct LovenseResult
	{
		public readonly bool Success;

		public readonly string Error;

		public readonly string ToySummary;

		private LovenseResult(bool success, string error, string toySummary)
		{
			Success = success;
			Error = error;
			ToySummary = toySummary;
		}

		public static LovenseResult Fail(string error)
		{
			return new LovenseResult(success: false, error, null);
		}

		public static LovenseResult Ok(string toySummary = null)
		{
			return new LovenseResult(success: true, null, toySummary);
		}
	}
	internal static class LovenseResponseParser
	{
		public static LovenseResult Parse(string body)
		{
			if (string.IsNullOrWhiteSpace(body))
			{
				return LovenseResult.Fail("Empty response body.");
			}
			int? num = ExtractIntField(body, "code");
			if (num == 502)
			{
				return LovenseResult.Fail("Game Mode is off. Enable it in Lovense Connect.");
			}
			if (num.HasValue && num.Value != 200)
			{
				return