Decompiled source of FaerieFlight v0.2.6

FaerieFlight.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FaerieFlight")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.6.0")]
[assembly: AssemblyInformationalVersion("0.2.6+f28d65039db0f6b3cd91fbb4364c06ba853f7391")]
[assembly: AssemblyProduct("FaerieFlight")]
[assembly: AssemblyTitle("FaerieFlight")]
[assembly: AssemblyVersion("0.2.6.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FaerieFlight
{
	[HarmonyPatch(typeof(FlashlightController), "Update")]
	internal static class FlashlightTumblePatch
	{
		private static readonly FieldRef<PlayerAvatar, bool> IsTumblingRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isTumbling");

		private static readonly FieldRef<PlayerAvatar, bool> IsCrouchingRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isCrouching");

		private static readonly FieldRef<PlayerAvatar, bool> IsSlidingRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isSliding");

		private static readonly FieldRef<PlayerAvatar, bool> IsLocalRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isLocal");

		private static readonly FieldRef<FlashlightController, bool> ActiveRef = AccessTools.FieldRefAccess<FlashlightController, bool>("active");

		private static readonly FieldRef<FlashlightController, bool> HideFlashlightRef = AccessTools.FieldRefAccess<FlashlightController, bool>("hideFlashlight");

		private static float _logAccum;

		[HarmonyPrefix]
		private static void Prefix(FlashlightController __instance, ref FlashState __state)
		{
			__state = default(FlashState);
			try
			{
				if (Plugin.Enabled.Value && Plugin.Flashlight.Value && !((Object)(object)RunManager.instance == (Object)null) && !SemiFunc.MenuLevel() && SemiFunc.RunIsLevel())
				{
					PlayerAvatar playerAvatar = __instance.PlayerAvatar;
					if (!((Object)(object)playerAvatar == (Object)null) && IsLocalRef.Invoke(playerAvatar))
					{
						__state.masked = true;
						__state.tumbling = IsTumblingRef.Invoke(playerAvatar);
						__state.crouching = IsCrouchingRef.Invoke(playerAvatar);
						__state.sliding = IsSlidingRef.Invoke(playerAvatar);
						IsTumblingRef.Invoke(playerAvatar) = false;
						IsCrouchingRef.Invoke(playerAvatar) = false;
						IsSlidingRef.Invoke(playerAvatar) = false;
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"FlashlightTumblePatch.Prefix: {arg}");
			}
		}

		[HarmonyPostfix]
		private static void Postfix(FlashlightController __instance, FlashState __state)
		{
			if (!__state.masked)
			{
				return;
			}
			try
			{
				PlayerAvatar playerAvatar = __instance.PlayerAvatar;
				if ((Object)(object)playerAvatar != (Object)null)
				{
					IsTumblingRef.Invoke(playerAvatar) = __state.tumbling;
					IsCrouchingRef.Invoke(playerAvatar) = __state.crouching;
					IsSlidingRef.Invoke(playerAvatar) = __state.sliding;
				}
				_logAccum += Time.deltaTime;
				if (_logAccum >= 1f)
				{
					_logAccum = 0f;
					Plugin.Log.LogInfo((object)($"[FloatDiag] flashlight active={ActiveRef.Invoke(__instance)} " + $"LightActive={__instance.LightActive} hide={HideFlashlightRef.Invoke(__instance)} " + $"wasTumbling={__state.tumbling} wasCrouching={__state.crouching}"));
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"FlashlightTumblePatch.Postfix: {arg}");
			}
		}
	}
	internal struct FlashState
	{
		public bool masked;

		public bool tumbling;

		public bool crouching;

		public bool sliding;
	}
	public class FloatDriver : MonoBehaviour, IOnEventCallback
	{
		private const float AffectTime = 5f;

		private const float RefreshInterval = 4f;

		private const float PrefabScanCooldown = 2f;

		private const float PrefabScanSettleDelay = 1.5f;

		private const float GrabOverrideTime = 0.2f;

		private const float ClimbSuppressTime = 0.5f;

		private const float MissingEffectRebroadcastDelay = 0.25f;

		private const string StaffItemKey = "Item Staff Zero Gravity";

		private const byte FloatEventCode = 117;

		private static readonly byte[] RepoReservedEventCodes = new byte[3] { 123, 124, 199 };

		private static readonly FieldRef<PlayerAvatar, PlayerTumble> AvatarTumbleRef = AccessTools.FieldRefAccess<PlayerAvatar, PlayerTumble>("tumble");

		private static readonly FieldRef<PlayerAvatar, bool> AvatarDeadSetRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("deadSet");

		private static readonly FieldRef<PlayerAvatar, bool> AvatarIsDisabledRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isDisabled");

		private static readonly FieldRef<PlayerAvatar, bool> AvatarIsTumblingRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isTumbling");

		private static readonly FieldRef<PlayerAvatar, bool> AvatarIsLocalRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isLocal");

		private static readonly FieldRef<PlayerTumble, PhysGrabObject> TumblePhysGrabObjectRef = AccessTools.FieldRefAccess<PlayerTumble, PhysGrabObject>("physGrabObject");

		private static readonly FieldRef<PhysGrabber, PhysGrabObject> GrabbedPhysGrabObjectRef = AccessTools.FieldRefAccess<PhysGrabber, PhysGrabObject>("grabbedPhysGrabObject");

		private static readonly FieldRef<LoadingUI, bool> LevelAnimationCompletedRef = AccessTools.FieldRefAccess<LoadingUI, bool>("levelAnimationCompleted");

		private static readonly FieldRef<SemiAffect, float> AffectTimerRef = AccessTools.FieldRefAccess<SemiAffect, float>("timer");

		private static readonly FieldRef<SemiAffect, float> AffectTimerTotalRef = AccessTools.FieldRefAccess<SemiAffect, float>("timerTotal");

		private GameObject? _affectPrefab;

		private bool _searched;

		private float _nextPrefabScan;

		private bool _photonCallbackRegistered;

		private readonly Dictionary<PlayerAvatar, SemiAffect> _active = new Dictionary<PlayerAvatar, SemiAffect>();

		private float _nextBroadcast;

		private float _logAccum;

		private static bool ShouldFloat()
		{
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				return false;
			}
			if (SemiFunc.MenuLevel())
			{
				return false;
			}
			return SemiFunc.RunIsLevel();
		}

		private static bool IsAlive(PlayerAvatar pa)
		{
			if (!AvatarDeadSetRef.Invoke(pa))
			{
				return !AvatarIsDisabledRef.Invoke(pa);
			}
			return false;
		}

		private static bool LevelFullyLoaded()
		{
			LoadingUI instance = LoadingUI.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return LevelAnimationCompletedRef.Invoke(instance);
			}
			return false;
		}

		private void OnEnable()
		{
			if (Array.IndexOf(RepoReservedEventCodes, (byte)117) >= 0)
			{
				Plugin.Log.LogError((object)($"FloatEventCode {(byte)117} collides with a REPO-reserved event (kick/ban); " + "networked float disabled to avoid kicking clients. Pick an unreserved code."));
				((Behaviour)this).enabled = false;
			}
			else
			{
				SceneManager.sceneLoaded += OnSceneLoaded;
			}
		}

		private void OnDisable()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			UnregisterPhotonCallback();
		}

		private void RegisterPhotonCallback()
		{
			if (!_photonCallbackRegistered)
			{
				PhotonNetwork.AddCallbackTarget((object)this);
				_photonCallbackRegistered = true;
			}
		}

		private void UnregisterPhotonCallback()
		{
			if (_photonCallbackRegistered)
			{
				PhotonNetwork.RemoveCallbackTarget((object)this);
				_photonCallbackRegistered = false;
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			_affectPrefab = null;
			_searched = false;
			_active.Clear();
			_nextBroadcast = 0f;
			_nextPrefabScan = Time.time + 1.5f;
			Plugin.Log.LogInfo((object)("[FloatDiag] scene '" + ((Scene)(ref scene)).name + "' loaded -> reset prefab + per-player state"));
		}

		private void Update()
		{
			try
			{
				bool flag = FloatGate.ShouldRunFloatWork(Plugin.Enabled.Value, ShouldFloat(), LevelFullyLoaded());
				GameObject val = (flag ? GetAffectPrefab() : null);
				GameDirector instance = GameDirector.instance;
				if (!flag || (Object)(object)val == (Object)null || (Object)(object)instance == (Object)null)
				{
					UnregisterPhotonCallback();
					if (_active.Count > 0)
					{
						DestroyAllEffects();
					}
					return;
				}
				if (FloatGate.PhotonCallbackDesired(flag, SemiFunc.IsMultiplayer()))
				{
					RegisterPhotonCallback();
				}
				MaintainEffects();
				SuppressClimbWhileFloating();
				if (SemiFunc.IsMasterClientOrSingleplayer())
				{
					CompensateGrabStrength(instance);
				}
				if (!SemiFunc.IsMultiplayer())
				{
					SpawnMissingLocal(val, instance);
				}
				else if (SemiFunc.IsMasterClientOrSingleplayer())
				{
					float time = Time.time;
					_nextBroadcast = FloatGate.NextBroadcastTime(_nextBroadcast, time, AnyEligiblePlayerMissingEffect(instance), 0.25f);
					if (time >= _nextBroadcast)
					{
						_nextBroadcast = time + 4f;
						BroadcastRoster(instance);
					}
				}
				MaybeLog(instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"FloatDriver.Update: {arg}");
			}
		}

		private void MaintainEffects()
		{
			if (_active.Count == 0)
			{
				return;
			}
			List<PlayerAvatar> list = null;
			foreach (KeyValuePair<PlayerAvatar, SemiAffect> item in _active)
			{
				PlayerAvatar key = item.Key;
				SemiAffect value = item.Value;
				if ((Object)(object)key == (Object)null || (Object)(object)value == (Object)null || !IsAlive(key))
				{
					if ((Object)(object)value != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)value).gameObject);
					}
					(list ?? (list = new List<PlayerAvatar>())).Add(item.Key);
				}
				else
				{
					AffectTimerRef.Invoke(value) = AffectTimerTotalRef.Invoke(value);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (PlayerAvatar item2 in list)
			{
				_active.Remove(item2);
			}
		}

		private void SuppressClimbWhileFloating()
		{
			foreach (KeyValuePair<PlayerAvatar, SemiAffect> item in _active)
			{
				PlayerAvatar key = item.Key;
				if (!((Object)(object)key == (Object)null) && !((Object)(object)item.Value == (Object)null) && AvatarIsLocalRef.Invoke(key))
				{
					if (FloatGate.ShouldSuppressClimb(Plugin.DisableWallClimbWhileFloating.Value, localEffectActive: true) && (Object)(object)key.physGrabber != (Object)null)
					{
						key.physGrabber.OverrideDisableSpecialGrabPowers(0.5f);
					}
					break;
				}
			}
		}

		private bool AnyEligiblePlayerMissingEffect(GameDirector director)
		{
			List<PlayerAvatar> playerList = director.PlayerList;
			for (int i = 0; i < playerList.Count; i++)
			{
				PlayerAvatar val = playerList[i];
				if (!((Object)(object)val == (Object)null) && IsAlive(val))
				{
					PlayerTumble val2 = AvatarTumbleRef.Invoke(val);
					if (!((Object)(object)val2 == (Object)null) && !((Object)(object)TumblePhysGrabObjectRef.Invoke(val2) == (Object)null) && (!_active.TryGetValue(val, out SemiAffect value) || (Object)(object)value == (Object)null))
					{
						return true;
					}
				}
			}
			return false;
		}

		private void DestroyAllEffects()
		{
			foreach (KeyValuePair<PlayerAvatar, SemiAffect> item in _active)
			{
				if ((Object)(object)item.Value != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)item.Value).gameObject);
				}
			}
			_active.Clear();
		}

		private void CompensateGrabStrength(GameDirector director)
		{
			if (!Plugin.RestoreGrabUpgrades.Value)
			{
				return;
			}
			List<PlayerAvatar> playerList = director.PlayerList;
			for (int i = 0; i < playerList.Count; i++)
			{
				PlayerAvatar val = playerList[i];
				if ((Object)(object)val == (Object)null || (Object)(object)val.physGrabber == (Object)null)
				{
					continue;
				}
				PhysGrabObject val2 = GrabbedPhysGrabObjectRef.Invoke(val.physGrabber);
				if (!((Object)(object)val2 == (Object)null))
				{
					SemiAffect value;
					float? num = FloatGate.CompensatedGrabStrength(_active.TryGetValue(val, out value) && (Object)(object)value != (Object)null, (Object)(object)((Component)val2).GetComponent<EnemyRigidbody>() != (Object)null, val.physGrabber.grabStrength, Plugin.StrengthSoftening.Value);
					if (num.HasValue)
					{
						float valueOrDefault = num.GetValueOrDefault();
						val2.OverrideGrabStrength(valueOrDefault, 0.2f);
					}
				}
			}
		}

		private void SpawnMissingLocal(GameObject prefab, GameDirector director)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			List<PlayerAvatar> playerList = director.PlayerList;
			for (int i = 0; i < playerList.Count; i++)
			{
				PlayerAvatar val = playerList[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				PlayerTumble val2 = AvatarTumbleRef.Invoke(val);
				PhysGrabObject val3 = (((Object)(object)val2 != (Object)null) ? TumblePhysGrabObjectRef.Invoke(val2) : null);
				if (FloatGate.CanSpawnEffect(Plugin.Enabled.Value, ShouldFloat(), IsAlive(val), (Object)(object)val2 != (Object)null, (Object)(object)val3 != (Object)null, _active.TryGetValue(val, out SemiAffect value) && (Object)(object)value != (Object)null))
				{
					GameObject val4 = Object.Instantiate<GameObject>(prefab, ((Component)val).transform.position, Quaternion.identity);
					SemiAffect component = val4.GetComponent<SemiAffect>();
					if ((Object)(object)component == (Object)null)
					{
						Object.Destroy((Object)(object)val4);
						continue;
					}
					component.direction = Vector3.up;
					component.positionOfOriginalAreaOfEffect = ((Component)val).transform.position;
					component.SetupSingleplayer(((Component)val).transform, val3, 5f, val);
					_active[val] = component;
				}
			}
		}

		private void BroadcastRoster(GameDirector director)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: 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_008b: Expected O, but got Unknown
			if (!PhotonNetwork.InRoom)
			{
				return;
			}
			List<PlayerAvatar> playerList = director.PlayerList;
			List<int> list = new List<int>(playerList.Count);
			for (int i = 0; i < playerList.Count; i++)
			{
				PlayerAvatar val = playerList[i];
				if (!((Object)(object)val == (Object)null) && IsAlive(val) && !((Object)(object)val.photonView == (Object)null))
				{
					list.Add(val.photonView.ViewID);
				}
			}
			if (list.Count != 0)
			{
				PhotonNetwork.RaiseEvent((byte)117, (object)list.ToArray(), new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)1
				}, SendOptions.SendReliable);
			}
		}

		public void OnEvent(EventData photonEvent)
		{
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			if (photonEvent.Code != 117)
			{
				return;
			}
			try
			{
				if (PhotonNetwork.MasterClient == null || photonEvent.Sender != PhotonNetwork.MasterClient.ActorNumber || !(photonEvent.CustomData is int[] array) || !FloatGate.ShouldRunFloatWork(Plugin.Enabled.Value, ShouldFloat(), LevelFullyLoaded()))
				{
					return;
				}
				bool value = Plugin.Enabled.Value;
				bool inFloatableLevel = ShouldFloat();
				GameObject affectPrefab = GetAffectPrefab();
				if ((Object)(object)affectPrefab == (Object)null)
				{
					return;
				}
				int[] array2 = array;
				foreach (int num in array2)
				{
					PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(num);
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					PlayerTumble val2 = AvatarTumbleRef.Invoke(val);
					PhysGrabObject val3 = (((Object)(object)val2 != (Object)null) ? TumblePhysGrabObjectRef.Invoke(val2) : null);
					if (FloatGate.CanSpawnEffect(value, inFloatableLevel, IsAlive(val), (Object)(object)val2 != (Object)null, (Object)(object)val3 != (Object)null, _active.TryGetValue(val, out SemiAffect value2) && (Object)(object)value2 != (Object)null))
					{
						GameObject val4 = Object.Instantiate<GameObject>(affectPrefab, ((Component)val).transform.position, Quaternion.identity);
						SemiAffect component = val4.GetComponent<SemiAffect>();
						if ((Object)(object)component == (Object)null)
						{
							Object.Destroy((Object)(object)val4);
							continue;
						}
						component.direction = Vector3.up;
						component.positionOfOriginalAreaOfEffect = ((Component)val).transform.position;
						component.Setup(num, 5f);
						_active[val] = component;
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"FloatDriver.OnEvent: {arg}");
			}
		}

		private GameObject? GetAffectPrefab()
		{
			if ((Object)(object)_affectPrefab != (Object)null)
			{
				return _affectPrefab;
			}
			if (!FloatGate.ShouldScanPrefab((Object)(object)_affectPrefab != (Object)null, Time.time, _nextPrefabScan))
			{
				return null;
			}
			_nextPrefabScan = Time.time + 2f;
			StatsManager instance = StatsManager.instance;
			if ((Object)(object)instance == (Object)null || instance.itemDictionary == null)
			{
				return null;
			}
			GameObject val = null;
			int num = 0;
			Item value;
			bool flag = instance.itemDictionary.TryGetValue("Item Staff Zero Gravity", out value) && (Object)(object)(val = TryResolveAffect(value)) != (Object)null;
			if ((Object)(object)val == (Object)null)
			{
				foreach (Item value2 in instance.itemDictionary.Values)
				{
					if (!((Object)(object)value2 == (Object)null) && value2.prefab != null && (value2.itemName ?? "").ToLowerInvariant().Contains("gravity"))
					{
						num++;
						val = TryResolveAffect(value2);
						if ((Object)(object)val != (Object)null)
						{
							break;
						}
					}
				}
			}
			if (!_searched || (Object)(object)val != (Object)null)
			{
				_searched = true;
				string text = (flag ? "via staff key" : $"via item DB: gravityItems={num}");
				Plugin.Log.LogInfo((object)("[FloatDiag] prefab " + text + " -> " + (((Object)(object)val != (Object)null) ? "FOUND" : "not yet")));
			}
			_affectPrefab = val;
			return _affectPrefab;
		}

		private static PrefabRef? ExtractProjectileRef(GameObject itemGO)
		{
			ItemStaffZeroGravity component = itemGO.GetComponent<ItemStaffZeroGravity>();
			if (!((Object)(object)component != (Object)null))
			{
				return null;
			}
			return component.projectilePrefab;
		}

		private static GameObject? TryResolveAffect(Item? item)
		{
			if ((Object)(object)item == (Object)null || item.prefab == null)
			{
				return null;
			}
			GameObject prefab;
			try
			{
				prefab = ((PrefabRef<GameObject>)(object)item.prefab).Prefab;
			}
			catch
			{
				return null;
			}
			if ((Object)(object)prefab == (Object)null)
			{
				return null;
			}
			PrefabRef val = ExtractProjectileRef(prefab);
			if (val == null)
			{
				return null;
			}
			GameObject prefab2;
			try
			{
				prefab2 = ((PrefabRef<GameObject>)(object)val).Prefab;
			}
			catch
			{
				return null;
			}
			if ((Object)(object)prefab2 == (Object)null)
			{
				return null;
			}
			SemiAreaOfEffect componentInChildren = prefab2.GetComponentInChildren<SemiAreaOfEffect>(true);
			if ((Object)(object)componentInChildren == (Object)null || componentInChildren.semiAffectPrefab == null)
			{
				return null;
			}
			GameObject prefab3;
			try
			{
				prefab3 = ((PrefabRef<GameObject>)(object)componentInChildren.semiAffectPrefab).Prefab;
			}
			catch
			{
				return null;
			}
			if (!((Object)(object)prefab3 != (Object)null) || !((Object)(object)prefab3.GetComponent<SemiAffectZeroGravity>() != (Object)null))
			{
				return null;
			}
			return prefab3;
		}

		private void MaybeLog(GameDirector director)
		{
			_logAccum += Time.deltaTime;
			if (_logAccum < 1f)
			{
				return;
			}
			_logAccum = 0f;
			bool flag = SemiFunc.IsMasterClientOrSingleplayer();
			List<PlayerAvatar> playerList = director.PlayerList;
			for (int i = 0; i < playerList.Count; i++)
			{
				PlayerAvatar val = playerList[i];
				if (!((Object)(object)val == (Object)null))
				{
					SemiAffect value;
					bool flag2 = _active.TryGetValue(val, out value) && (Object)(object)value != (Object)null;
					Plugin.Log.LogInfo((object)($"[FloatDiag] master={flag} p{i} local={AvatarIsLocalRef.Invoke(val)} " + $"alive={IsAlive(val)} tumbling={AvatarIsTumblingRef.Invoke(val)} effect={flag2}"));
				}
			}
		}
	}
	public static class FloatGate
	{
		public static bool CanSpawnEffect(bool modEnabled, bool inFloatableLevel, bool avatarAlive, bool tumbleLinked, bool physGrabObjectLinked, bool effectAlreadyActive)
		{
			if (modEnabled && inFloatableLevel && avatarAlive && tumbleLinked && physGrabObjectLinked)
			{
				return !effectAlreadyActive;
			}
			return false;
		}

		public static bool ShouldScanPrefab(bool prefabCached, float now, float nextScanAllowedAt)
		{
			if (!prefabCached)
			{
				return now >= nextScanAllowedAt;
			}
			return false;
		}

		public static bool ShouldRunFloatWork(bool modEnabled, bool inFloatableLevel, bool levelFullyLoaded)
		{
			return modEnabled && inFloatableLevel && levelFullyLoaded;
		}

		public static bool PhotonCallbackDesired(bool floatWorkActive, bool isMultiplayer)
		{
			return floatWorkActive && isMultiplayer;
		}

		public static bool ShouldSuppressClimb(bool disableClimbWhileFloating, bool localEffectActive)
		{
			return disableClimbWhileFloating && localEffectActive;
		}

		public static float NextBroadcastTime(float scheduledAt, float now, bool eligiblePlayerMissingEffect, float missingEffectDelay)
		{
			if (!eligiblePlayerMissingEffect)
			{
				return scheduledAt;
			}
			float num = now + missingEffectDelay;
			if (!(num < scheduledAt))
			{
				return scheduledAt;
			}
			return num;
		}

		public static float? CompensatedGrabStrength(bool effectActive, bool grabbedIsEnemy, float upgradedStrength, float softening)
		{
			if (!effectActive || grabbedIsEnemy)
			{
				return null;
			}
			if (upgradedStrength <= 1f)
			{
				return null;
			}
			float num = ((softening < 0f) ? 0f : ((softening > 1f) ? 1f : softening));
			float num2 = (float)Math.Sqrt(upgradedStrength);
			return upgradedStrength + (num2 - upgradedStrength) * num;
		}
	}
	[BepInPlugin("darkharasho.FaerieFlight", "FaerieFlight", "0.2.6")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> Flashlight;

		internal static ConfigEntry<bool> RestoreGrabUpgrades;

		internal static ConfigEntry<float> StrengthSoftening;

		internal static ConfigEntry<bool> DisableWallClimbWhileFloating;

		private void Awake()
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master on/off switch for permanent floating. Best set on the host; the host drives the float for every player. (All players should run the mod.)");
			Flashlight = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Flashlight", true, "Keep your flashlight on while floating. The game normally turns it off during tumble; since it's a dark game, this keeps it lit.");
			RestoreGrabUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RestoreGrabUpgrades", true, "The game ignores Strength upgrades while a player is tumbling, so permanent float would leave them permanently dead. Restores them (softened) while floating. Host-side setting; grabbing enemies keeps vanilla behavior either way.");
			StrengthSoftening = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StrengthSoftening", 0.65f, new ConfigDescription("How much to soften restored Strength upgrades while floating: 0 = full upgrade strength, 1 = square-root curve. Floating grabbers take the grab's recoil, so full strength can fling you.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DisableWallClimbWhileFloating = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DisableWallClimbWhileFloating", true, "The Tumble Climb upgrade turns any wall grab into a climb; while floating, the climb spring yanks you out of the air to the grab point (feels like being dragged down to the ground). Suppresses climbing while you float — wall grabs show the game's 'disabled' zap instead. Per-player setting; turn off to keep wall-climbing.");
			new Harmony("darkharasho.FaerieFlight").PatchAll();
			((Component)this).gameObject.AddComponent<FloatDriver>();
			Log.LogInfo((object)"FaerieFlight v0.2.6 loaded.");
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "darkharasho.FaerieFlight";

		public const string PLUGIN_NAME = "FaerieFlight";

		public const string PLUGIN_VERSION = "0.2.6";
	}
}