Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FaerieFlight v0.2.1
FaerieFlight.dll
Decompiled 2 days agousing 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"; } }