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_005c: 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_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0123: 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)
if (!character.IsLocal || !HostHasMod)
{
return;
}
bool flag = false;
if ((!RequireModifierKey.Value) ? Input.GetKeyDown(flyToggleKey) : (Input.GetKey(flyToggleModifierKey) && Input.GetKeyDown(flyToggleKey)))
{
bool flag2 = 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 (flag2 && !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<bool> RequireModifierKey;
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 (only used if RequireModifierKey is true)");
RequireModifierKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Hotkey", "RequireModifierKey", true, "Whether to require the modifier key to be held when toggling 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)");
}
}