Decompiled source of So Fly v1.3.1

tony4twentys-So Fly.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("So Fly")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("So Fly")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("be90c841-fb92-42ee-80b7-fcedee6eb28f")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[BepInPlugin("tony4twentys.So_Fly", "So Fly", "1.3.1")]
public class SoFlyPlugin : BaseUnityPlugin, IInRoomCallbacks, IMatchmakingCallbacks
{
	[HarmonyPatch(typeof(CharacterMovement), "Start")]
	public class CharacterMovement_Start_Patch
	{
		private static void Postfix(CharacterMovement __instance)
		{
			if ((Object)(object)((Component)__instance).gameObject.GetComponent<FlyController>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<FlyController>();
			}
		}
	}

	[HarmonyPatch(typeof(CharacterMovement), "CheckFallDamage")]
	public class CharacterMovement_CheckFallDamage_Patch
	{
		private static bool Prefix(CharacterMovement __instance)
		{
			FlyController component = ((Component)__instance).GetComponent<FlyController>();
			if ((Object)(object)component != (Object)null && component.NoFallDamageActive)
			{
				ManualLogSource loggerInstance = LoggerInstance;
				if (loggerInstance != null)
				{
					loggerInstance.LogInfo((object)"Fall damage prevented: post-fly immunity active.");
				}
				return false;
			}
			return true;
		}
	}

	public class FlyController : MonoBehaviour
	{
		private GameObject canvasObject;

		private GameObject currentTextObj;

		private Character character;

		private bool isFlying = false;

		private float[] flySpeeds;

		private int currentSpeedIndex = 0;

		private KeyCode flyToggleModifierKey;

		private KeyCode flyToggleKey;

		private KeyCode speedCycleKey;

		private float noFallDamageUntil = 0f;

		private float CurrentFlySpeed => flySpeeds[currentSpeedIndex];

		public bool NoFallDamageActive => Time.time < noFallDamageUntil;

		private void Start()
		{
			InitializeFlySpeeds();
			character = ((Component)this).GetComponent<Character>();
			SetupNotificationCanvas();
			InitializeControls();
		}

		private void InitializeFlySpeeds()
		{
			flySpeeds = (from s in SpeedConfig.Value.Split(new char[1] { ',' })
				select float.TryParse(s.Trim(), out var result) ? result : 0f into f
				where f > 0f
				select f).ToArray();
			if (flySpeeds.Length == 0)
			{
				flySpeeds = new float[5] { 25f, 50f, 75f, 100f, 200f };
				LoggerInstance.LogWarning((object)"No valid fly speeds found in config. Using defaults.");
			}
		}

		private void InitializeControls()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			flyToggleModifierKey = FlyToggleModifier.Value;
			flyToggleKey = FlyToggleKey.Value;
			speedCycleKey = FlySpeedCycleKey.Value;
		}

		private void Update()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			if (!character.IsLocal || !HostHasMod)
			{
				return;
			}
			if (Input.GetKey(flyToggleModifierKey) && Input.GetKeyDown(flyToggleKey))
			{
				bool flag = isFlying;
				isFlying = !isFlying;
				LoggerInstance.LogInfo((object)(isFlying ? "Fly mode ENABLED" : "Fly mode DISABLED"));
				ShowPopup(isFlying ? "SO FLY ON" : "SO FLY OFF", ParseHexColor(isFlying ? SoFlyOnTextColor.Value : SoFlyOffTextColor.Value));
				ApplyFlyingPhysics();
				if (flag && !isFlying)
				{
					ArmNoFallDamage(NoFallDamageDuration.Value);
				}
			}
			if (isFlying && Input.GetKeyDown(speedCycleKey))
			{
				currentSpeedIndex = (currentSpeedIndex + 1) % flySpeeds.Length;
				LoggerInstance.LogInfo((object)$"Fly speed set to {CurrentFlySpeed} m/s");
			}
		}

		private void ApplyFlyingPhysics()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			foreach (Bodypart part in character.refs.ragdoll.partList)
			{
				part.Rig.useGravity = !isFlying;
				part.Rig.isKinematic = isFlying;
				if (!isFlying)
				{
					part.Rig.linearVelocity = Vector3.zero;
				}
			}
		}

		private void FixedUpdate()
		{
			if (isFlying)
			{
				ApplyFlyingMovement();
			}
		}

		private void ApplyFlyingMovement()
		{
			//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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: 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_00bd: 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_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = Vector3.zero;
			Vector3 lookDirection = character.data.lookDirection;
			Vector3 val2 = Vector3.Cross(Vector3.up, lookDirection);
			Vector3 normalized = ((Vector3)(ref val2)).normalized;
			val += lookDirection * character.input.movementInput.y;
			val += normalized * character.input.movementInput.x;
			if (character.input.crouchIsPressed)
			{
				val += Vector3.down;
			}
			if (Input.GetKey((KeyCode)32))
			{
				val += Vector3.up;
			}
			val = ((Vector3)(ref val)).normalized * CurrentFlySpeed;
			Transform transform = ((Component)character).transform;
			transform.position += val * Time.fixedDeltaTime;
		}

		private void ArmNoFallDamage(float seconds)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			noFallDamageUntil = Time.time + seconds;
			LoggerInstance.LogInfo((object)$"No-fall-damage window armed for {seconds} seconds.");
			ShowPopup("SO FLY OFF", ParseHexColor(SoFlyOffTextColor.Value));
		}

		private void SetupNotificationCanvas()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			if (!((Object)(object)canvasObject != (Object)null))
			{
				canvasObject = new GameObject("FlyNotificationCanvas");
				Canvas val = canvasObject.AddComponent<Canvas>();
				val.renderMode = (RenderMode)0;
				val.sortingOrder = 999;
				canvasObject.AddComponent<CanvasScaler>();
				canvasObject.AddComponent<GraphicRaycaster>();
				Object.DontDestroyOnLoad((Object)(object)canvasObject);
			}
		}

		private void ShowPopup(string message, Color color)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: 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_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if (!HideAllNotifications.Value)
			{
				if ((Object)(object)currentTextObj != (Object)null)
				{
					Object.Destroy((Object)(object)currentTextObj);
				}
				currentTextObj = new GameObject("FlyText");
				currentTextObj.transform.SetParent(canvasObject.transform);
				TextMeshProUGUI val = currentTextObj.AddComponent<TextMeshProUGUI>();
				SetupTextMeshPro(val, message, TextSize.Value, (TextAlignmentOptions)514, color);
				RectTransform component = ((Component)val).GetComponent<RectTransform>();
				component.sizeDelta = new Vector2(800f, 200f);
				component.anchoredPosition = new Vector2(0f, -200f);
				component.anchorMin = new Vector2(0.5f, 0.5f);
				component.anchorMax = new Vector2(0.5f, 0.5f);
				component.pivot = new Vector2(0.5f, 0.5f);
				Object.Destroy((Object)(object)currentTextObj, 5f);
			}
		}

		private void SetupTextMeshPro(TextMeshProUGUI tmp, string message, int fontSize, TextAlignmentOptions alignment, Color color)
		{
			//IL_0018: 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)
			((TMP_Text)tmp).text = message.ToUpper();
			((TMP_Text)tmp).fontSize = fontSize;
			((TMP_Text)tmp).alignment = alignment;
			((Graphic)tmp).color = color;
			((Graphic)tmp).raycastTarget = false;
			((TMP_Text)tmp).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)tmp).fontStyle = (FontStyles)1;
			if ((Object)(object)GameFont != (Object)null)
			{
				((TMP_Text)tmp).font = GameFont;
				return;
			}
			ManualLogSource loggerInstance = LoggerInstance;
			if (loggerInstance != null)
			{
				loggerInstance.LogWarning((object)"Game font not available, using default font");
			}
		}

		private Color ParseHexColor(string hexColor)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string text = hexColor.TrimStart(new char[1] { '#' });
				int num = Convert.ToInt32(text.Substring(0, 2), 16);
				int num2 = Convert.ToInt32(text.Substring(2, 2), 16);
				int num3 = Convert.ToInt32(text.Substring(4, 2), 16);
				return new Color((float)num / 255f, (float)num2 / 255f, (float)num3 / 255f, 1f);
			}
			catch
			{
				ManualLogSource loggerInstance = LoggerInstance;
				if (loggerInstance != null)
				{
					loggerInstance.LogWarning((object)("Failed to parse hex color '" + hexColor + "', using cyan as fallback"));
				}
				return Color.cyan;
			}
		}
	}

	private const string ROOM_CFG_KEY = "SOFLY_CFG_V1";

	public static ConfigEntry<KeyCode> FlyToggleKey;

	public static ConfigEntry<KeyCode> FlyToggleModifier;

	public static ConfigEntry<KeyCode> FlySpeedCycleKey;

	public static ConfigEntry<string> SpeedConfig;

	public static ConfigEntry<bool> HideAllNotifications;

	public static ConfigEntry<int> TextSize;

	public static ConfigEntry<string> SoFlyOnTextColor;

	public static ConfigEntry<string> SoFlyOffTextColor;

	public static ConfigEntry<int> NoFallDamageDuration;

	public static ManualLogSource LoggerInstance;

	private static TMP_FontAsset _gameFontAsset;

	private static bool _fontSearchAttempted;

	public static bool HostHasMod { get; private set; }

	public static bool IsHost { get; private set; }

	public static TMP_FontAsset GameFont
	{
		get
		{
			if ((Object)(object)_gameFontAsset == (Object)null && !_fontSearchAttempted)
			{
				_gameFontAsset = FindGameFont();
				_fontSearchAttempted = true;
			}
			return _gameFontAsset;
		}
	}

	private static TMP_FontAsset FindGameFont()
	{
		//IL_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			ManualLogSource loggerInstance = LoggerInstance;
			if (loggerInstance != null)
			{
				loggerInstance.LogInfo((object)$"Found {array.Length} TMP fonts in resources");
			}
			TMP_FontAsset val = ((IEnumerable<TMP_FontAsset>)array).FirstOrDefault((Func<TMP_FontAsset, bool>)delegate(TMP_FontAsset fontAsset)
			{
				//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)
				FaceInfo faceInfo2 = ((TMP_Asset)fontAsset).faceInfo;
				return ((FaceInfo)(ref faceInfo2)).familyName == "Daruma Drop One";
			});
			if ((Object)(object)val != (Object)null)
			{
				ManualLogSource loggerInstance2 = LoggerInstance;
				if (loggerInstance2 != null)
				{
					loggerInstance2.LogInfo((object)("Found game font by family name: " + ((Object)val).name));
				}
				return val;
			}
			ManualLogSource loggerInstance3 = LoggerInstance;
			if (loggerInstance3 != null)
			{
				loggerInstance3.LogWarning((object)"Available fonts:");
			}
			foreach (TMP_FontAsset item in array.Where((TMP_FontAsset f) => ((Object)f).name.Contains("SDF")))
			{
				ManualLogSource loggerInstance4 = LoggerInstance;
				if (loggerInstance4 != null)
				{
					string[] obj = new string[5]
					{
						"  - ",
						((Object)item).name,
						" (Family: ",
						null,
						null
					};
					FaceInfo faceInfo = ((TMP_Asset)item).faceInfo;
					obj[3] = ((FaceInfo)(ref faceInfo)).familyName;
					obj[4] = ")";
					loggerInstance4.LogWarning((object)string.Concat(obj));
				}
			}
			return null;
		}
		catch (Exception ex)
		{
			ManualLogSource loggerInstance5 = LoggerInstance;
			if (loggerInstance5 != null)
			{
				loggerInstance5.LogWarning((object)("Error finding game font: " + ex.Message));
			}
			return null;
		}
	}

	private void Awake()
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Expected O, but got Unknown
		LoggerInstance = ((BaseUnityPlugin)this).Logger;
		InitializeConfiguration();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"So Fly loaded!");
		Harmony val = new Harmony("tony4twentys.So_Fly");
		val.PatchAll();
	}

	private void OnEnable()
	{
		PhotonNetwork.AddCallbackTarget((object)this);
	}

	private void OnDisable()
	{
		PhotonNetwork.RemoveCallbackTarget((object)this);
	}

	public void OnJoinedRoom()
	{
		if (PhotonNetwork.IsMasterClient)
		{
			ManualLogSource loggerInstance = LoggerInstance;
			if (loggerInstance != null)
			{
				loggerInstance.LogInfo((object)"So Fly: Joined room as HOST");
			}
			PublishConfig();
		}
		else
		{
			ManualLogSource loggerInstance2 = LoggerInstance;
			if (loggerInstance2 != null)
			{
				loggerInstance2.LogInfo((object)"So Fly: Joined room as CLIENT; reading config");
			}
			TryReadConfigFromRoom();
		}
	}

	public void OnCreatedRoom()
	{
	}

	public void OnCreateRoomFailed(short returnCode, string message)
	{
	}

	public void OnFriendListUpdate(List<FriendInfo> friendList)
	{
	}

	public void OnJoinRandomFailed(short returnCode, string message)
	{
	}

	public void OnJoinRoomFailed(short returnCode, string message)
	{
	}

	public void OnLeftRoom()
	{
	}

	void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer)
	{
		if (PhotonNetwork.IsMasterClient)
		{
			PublishConfig();
		}
	}

	void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer)
	{
	}

	void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
	{
		if (propertiesThatChanged != null && ((Dictionary<object, object>)(object)propertiesThatChanged).ContainsKey((object)"SOFLY_CFG_V1"))
		{
			HostHasMod = true;
			IsHost = false;
			ManualLogSource loggerInstance = LoggerInstance;
			if (loggerInstance != null)
			{
				loggerInstance.LogInfo((object)"So Fly: Host handshake completed - fly mode enabled");
			}
		}
	}

	void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
	{
	}

	void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient)
	{
		if (PhotonNetwork.IsMasterClient)
		{
			PublishConfig();
		}
	}

	private void TryReadConfigFromRoom()
	{
		Room currentRoom = PhotonNetwork.CurrentRoom;
		if (currentRoom != null && ((RoomInfo)currentRoom).CustomProperties != null && ((Dictionary<object, object>)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"SOFLY_CFG_V1", out object _))
		{
			HostHasMod = true;
			IsHost = false;
			ManualLogSource loggerInstance = LoggerInstance;
			if (loggerInstance != null)
			{
				loggerInstance.LogInfo((object)"So Fly: Host handshake completed - fly mode enabled");
			}
		}
	}

	private void PublishConfig()
	{
		//IL_001f: 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_0038: Expected O, but got Unknown
		Room currentRoom = PhotonNetwork.CurrentRoom;
		if (currentRoom != null)
		{
			HostHasMod = true;
			IsHost = true;
			Hashtable val = new Hashtable { [(object)"SOFLY_CFG_V1"] = "SOFLY_ENABLED" };
			currentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
			ManualLogSource loggerInstance = LoggerInstance;
			if (loggerInstance != null)
			{
				loggerInstance.LogInfo((object)"So Fly: Host published config - fly mode enabled");
			}
		}
	}

	private void InitializeConfiguration()
	{
		FlyToggleModifier = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkey", "FlyToggleModifier", (KeyCode)308, "Modifier key to toggle fly mode");
		FlyToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkey", "FlyToggleKey", (KeyCode)102, "Key to toggle fly mode");
		FlySpeedCycleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkey", "FlySpeedCycleKey", (KeyCode)118, "Key to cycle fly speed");
		SpeedConfig = ((BaseUnityPlugin)this).Config.Bind<string>("General", "FlySpeeds", "25,50,75,100,200", "Comma-separated list of fly speeds (m/s)");
		NoFallDamageDuration = ((BaseUnityPlugin)this).Config.Bind<int>("General", "NoFallDamageDuration", 30, "Duration of fall damage immunity after leaving fly mode (seconds)");
		HideAllNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "HideAllNotifications", false, "Hide all on-screen notifications");
		TextSize = ((BaseUnityPlugin)this).Config.Bind<int>("UI", "TextSize", 60, "Size of notification text");
		SoFlyOnTextColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "SoFlyOnTextColor", "#00FF00", "Color of 'SO FLY ON' notification text (hex format, e.g. #00FF00)");
		SoFlyOffTextColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "SoFlyOffTextColor", "#00FFFF", "Color of 'SO FLY OFF' notification text (hex format, e.g. #00FFFF)");
	}
}