Decompiled source of FaerieFlight v0.2.1

FaerieFlight.dll

Decompiled 2 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.1.0")]
[assembly: AssemblyInformationalVersion("0.2.1+af53bf0a480434b4286152982db8af13b820e15a")]
[assembly: AssemblyProduct("FaerieFlight")]
[assembly: AssemblyTitle("FaerieFlight")]
[assembly: AssemblyVersion("0.2.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace 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 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<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 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 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;
				PhotonNetwork.AddCallbackTarget((object)this);
			}
		}

		private void OnDisable()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			PhotonNetwork.RemoveCallbackTarget((object)this);
		}

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

		private void Update()
		{
			try
			{
				int num;
				object obj;
				if (Plugin.Enabled.Value)
				{
					num = (ShouldFloat() ? 1 : 0);
					if (num != 0)
					{
						obj = GetAffectPrefab();
						goto IL_0020;
					}
				}
				else
				{
					num = 0;
				}
				obj = null;
				goto IL_0020;
				IL_0020:
				GameObject val = (GameObject)obj;
				GameDirector instance = GameDirector.instance;
				if (num == 0 || (Object)(object)val == (Object)null || (Object)(object)instance == (Object)null)
				{
					if (_active.Count > 0)
					{
						DestroyAllEffects();
					}
					return;
				}
				MaintainEffects();
				if (!SemiFunc.IsMultiplayer())
				{
					SpawnMissingLocal(val, instance);
				}
				else if (SemiFunc.IsMasterClientOrSingleplayer())
				{
					float time = Time.time;
					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 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 SpawnMissingLocal(GameObject prefab, GameDirector director)
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			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) || (_active.TryGetValue(val, out SemiAffect value) && (Object)(object)value != (Object)null))
				{
					continue;
				}
				PlayerTumble val2 = AvatarTumbleRef.Invoke(val);
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				PhysGrabObject val3 = TumblePhysGrabObjectRef.Invoke(val2);
				if (!((Object)(object)val3 == (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_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			if (photonEvent.Code != 117 || PhotonNetwork.MasterClient == null || photonEvent.Sender != PhotonNetwork.MasterClient.ActorNumber || !(photonEvent.CustomData is int[] array))
			{
				return;
			}
			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) && IsAlive(val) && (!_active.TryGetValue(val, out SemiAffect value) || !((Object)(object)value != (Object)null)))
				{
					GameObject val2 = Object.Instantiate<GameObject>(affectPrefab, ((Component)val).transform.position, Quaternion.identity);
					SemiAffect component = val2.GetComponent<SemiAffect>();
					if ((Object)(object)component == (Object)null)
					{
						Object.Destroy((Object)(object)val2);
						continue;
					}
					component.direction = Vector3.up;
					component.positionOfOriginalAreaOfEffect = ((Component)val).transform.position;
					component.Setup(num, 5f);
					_active[val] = component;
				}
			}
		}

		private GameObject? GetAffectPrefab()
		{
			if ((Object)(object)_affectPrefab != (Object)null)
			{
				return _affectPrefab;
			}
			StatsManager instance = StatsManager.instance;
			if ((Object)(object)instance == (Object)null || instance.itemDictionary == null)
			{
				return null;
			}
			int num = 0;
			GameObject val = null;
			foreach (Item value in instance.itemDictionary.Values)
			{
				if ((Object)(object)value == (Object)null || value.prefab == null || !(value.itemName ?? "").ToLowerInvariant().Contains("gravity"))
				{
					continue;
				}
				num++;
				GameObject prefab;
				try
				{
					prefab = ((PrefabRef<GameObject>)(object)value.prefab).Prefab;
				}
				catch
				{
					continue;
				}
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				PrefabRef val2 = ExtractProjectileRef(prefab);
				if (val2 == null)
				{
					continue;
				}
				GameObject prefab2;
				try
				{
					prefab2 = ((PrefabRef<GameObject>)(object)val2).Prefab;
				}
				catch
				{
					continue;
				}
				if ((Object)(object)prefab2 == (Object)null)
				{
					continue;
				}
				SemiAreaOfEffect componentInChildren = prefab2.GetComponentInChildren<SemiAreaOfEffect>(true);
				if (!((Object)(object)componentInChildren == (Object)null) && componentInChildren.semiAffectPrefab != null)
				{
					GameObject prefab3;
					try
					{
						prefab3 = ((PrefabRef<GameObject>)(object)componentInChildren.semiAffectPrefab).Prefab;
					}
					catch
					{
						continue;
					}
					if ((Object)(object)prefab3 != (Object)null && (Object)(object)prefab3.GetComponent<SemiAffectZeroGravity>() != (Object)null)
					{
						val = prefab3;
						break;
					}
				}
			}
			if (!_searched || (Object)(object)val != (Object)null)
			{
				_searched = true;
				Plugin.Log.LogInfo((object)string.Format("[FloatDiag] prefab via item DB: gravityItems={0} -> {1}", num, ((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 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}"));
				}
			}
		}
	}
	[BepInPlugin("darkharasho.FaerieFlight", "FaerieFlight", "0.2.1")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> Flashlight;

		private void Awake()
		{
			//IL_0050: 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.");
			new Harmony("darkharasho.FaerieFlight").PatchAll();
			((Component)this).gameObject.AddComponent<FloatDriver>();
			Log.LogInfo((object)"FaerieFlight v0.2.1 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.1";
	}
}