The BepInEx console will not appear when launching like it does for other games on Thunderstore (you can turn it back on in your BepInEx.cfg file). If your PEAK crashes on startup, add -dx12 to your launch parameters.
Decompiled source of Enhanced Balloon and Parasol Control v1.0.0
tony4twentys-Enhanced Balloon and Parasol Control.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("PEAK Balloon Controls")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PEAK Balloon Controls")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4d0177cf-accf-427f-91a2-0e1309ac53fa")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace PeakMods.EnhancedBalloonParasolControl; [BepInPlugin("tony4twentys.Enhanced_Balloon_and_Parasol_Control", "Enhanced Balloon and Parasol Control", "1.0.0")] public sealed class EnhancedBalloonParasolControl : BaseUnityPlugin, IInRoomCallbacks, IMatchmakingCallbacks { [Serializable] public struct HostCfg { public float airForceMult; public float boostedAirForceMult; } [HarmonyPatch(typeof(CharacterMovement), "SetMovementState")] private static class Patch_SetMovementState_PopperAndAirSprintDrain { private static void Postfix(CharacterMovement __instance) { if (!HandshakeActive()) { return; } SingleCrouchEdgeAction(__instance); Character component = ((Component)__instance).GetComponent<Character>(); if ((Object)(object)component == (Object)null || component.data.isGrounded || (!IsOfficiallyFlying(__instance) && !IsParasolOpen(component)) || !component.data.isSprinting) { return; } if (_miUseStamina != null) { try { _miUseStamina.Invoke(component, new object[2] { __instance.sprintStaminaUsage * Time.deltaTime, true }); return; } catch (Exception ex) { EnhancedBalloonParasolControl instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogDebug((object)("UseStamina reflection failed: " + ex.Message)); } return; } } CharacterRefs refs = component.refs; if (refs != null) { PhotonView view = refs.view; if (((view != null) ? new bool?(view.IsMine) : null).GetValueOrDefault()) { CharacterData data = component.data; data.currentStamina -= __instance.sprintStaminaUsage * Time.deltaTime; component.ClampStamina(); } } } } [HarmonyPatch(typeof(CharacterMovement), "GetMovementForce")] private static class Patch_GetMovementForce_FlyingBoost { private static void Postfix(CharacterMovement __instance, ref float __result) { if (HandshakeActive()) { Character component = ((Component)__instance).GetComponent<Character>(); if (!((Object)(object)component == (Object)null) && !component.data.isGrounded && (IsOfficiallyFlying(__instance) || IsParasolOpen(component))) { bool flag = component.data.currentStamina >= 0.005f; float num = ((component.data.isSprinting && flag) ? Synced.boostedAirForceMult : Synced.airForceMult); __result *= Mathf.Max(0.01f, num); } } } } internal static EnhancedBalloonParasolControl Instance; internal static Harmony Harmony; private const string ROOM_CFG_KEY = "EBPC_CFG_V1"; internal static HostCfg Synced = new HostCfg { airForceMult = 2f, boostedAirForceMult = 3f }; private static bool s_roomCfgPresent; private static bool s_inRoomCached; internal static ConfigEntry<float> C_AirForceMultiplier; internal static ConfigEntry<float> C_BoostedAirForceMultiplier; private static readonly Dictionary<int, bool> s_crouchHeld = new Dictionary<int, bool>(); private static FieldInfo _fiTiedBalloons; private static MethodInfo _miPop; private static MethodInfo _miUseStamina; private void Awake() { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown Instance = this; C_AirForceMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Movement", "AirForceMultiplier", 2f, "Multiply movement force WHEN balloon lift or parasol glide is active."); C_BoostedAirForceMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Movement", "BoostedAirForceMultiplier", 3f, "Multiply movement force WHEN flying/gliding AND sprinting (consumes stamina like sprint)."); Synced = BuildFromHostConfig(); TryBindReflection(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[EnhancedBalloonParasol] Reflection mode (no publicizer). Consider MSBuild.Publicizer or IgnoresAccessChecksTo for direct calls."); Harmony = new Harmony("tony4twentys.Enhanced_Balloon_and_Parasol_Control"); Harmony.PatchAll(typeof(Patch_SetMovementState_PopperAndAirSprintDrain)); Harmony.PatchAll(typeof(Patch_GetMovementForce_FlyingBoost)); C_AirForceMultiplier.SettingChanged += delegate { OnHostConfigChanged(); }; C_BoostedAirForceMultiplier.SettingChanged += delegate { OnHostConfigChanged(); }; SceneManager.activeSceneChanged += OnSceneChanged; } private void OnEnable() { PhotonNetwork.AddCallbackTarget((object)this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget((object)this); } private void OnDestroy() { SceneManager.activeSceneChanged -= OnSceneChanged; } private void OnSceneChanged(Scene oldScene, Scene newScene) { TryReadConfigFromRoom(); } public void OnJoinedRoom() { s_inRoomCached = true; if (PhotonNetwork.IsMasterClient) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[EBPC] Joined room as HOST — publishing config."); PublishConfig(); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[EBPC] 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() { s_inRoomCached = false; s_roomCfgPresent = false; } 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)"EBPC_CFG_V1") && propertiesThatChanged[(object)"EBPC_CFG_V1"] is string s) { HostCfg synced = Synced; Synced = UnpackCfg(s); s_roomCfgPresent = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[EBPC] Config updated from room: air={Synced.airForceMult} boosted={Synced.boostedAirForceMult} (was {synced.airForceMult}/{synced.boostedAirForceMult})"); } } void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { } void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient) { if (PhotonNetwork.IsMasterClient) { PublishConfig(); } } private HostCfg BuildFromHostConfig() { HostCfg result = default(HostCfg); result.airForceMult = Mathf.Max(0.01f, C_AirForceMultiplier.Value); result.boostedAirForceMult = Mathf.Max(0.01f, C_BoostedAirForceMultiplier.Value); return result; } private void OnHostConfigChanged() { if (PhotonNetwork.IsMasterClient) { PublishConfig(); } } private void PublishConfig() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null) { Synced = BuildFromHostConfig(); string text = PackCfg(Synced); Hashtable val = new Hashtable { [(object)"EBPC_CFG_V1"] = text }; currentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); s_roomCfgPresent = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[EBPC] Host published config: air={Synced.airForceMult} boosted={Synced.boostedAirForceMult}"); } } private void TryReadConfigFromRoom() { Room currentRoom = PhotonNetwork.CurrentRoom; object value; if (currentRoom == null || ((RoomInfo)currentRoom).CustomProperties == null) { s_roomCfgPresent = false; } else if (((Dictionary<object, object>)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"EBPC_CFG_V1", out value) && value is string s) { Synced = UnpackCfg(s); s_roomCfgPresent = true; } else { s_roomCfgPresent = false; } } private static string PackCfg(HostCfg c) { CultureInfo invariantCulture = CultureInfo.InvariantCulture; return c.airForceMult.ToString(invariantCulture) + "|" + c.boostedAirForceMult.ToString(invariantCulture); } private static HostCfg UnpackCfg(string s) { CultureInfo invariantCulture = CultureInfo.InvariantCulture; string[] array = (s ?? string.Empty).Split(new char[1] { '|' }); HostCfg hostCfg = default(HostCfg); hostCfg.airForceMult = 2f; hostCfg.boostedAirForceMult = 3f; HostCfg result = hostCfg; if (array.Length >= 2) { float.TryParse(array[0], NumberStyles.Float, invariantCulture, out result.airForceMult); float.TryParse(array[1], NumberStyles.Float, invariantCulture, out result.boostedAirForceMult); } result.airForceMult = Mathf.Max(0.01f, result.airForceMult); result.boostedAirForceMult = Mathf.Max(0.01f, result.boostedAirForceMult); return result; } internal static bool HandshakeActive() { if (!s_inRoomCached && !PhotonNetwork.InRoom) { return true; } return s_roomCfgPresent; } private static void TryBindReflection() { Type type = AccessTools.TypeByName("CharacterBalloons"); if (type != null) { _fiTiedBalloons = AccessTools.Field(type, "tiedBalloons"); } Type type2 = AccessTools.TypeByName("TiedBalloon"); if (type2 != null) { _miPop = AccessTools.Method(type2, "Pop", (Type[])null, (Type[])null); } Type type3 = AccessTools.TypeByName("Character"); if (type3 != null) { _miUseStamina = AccessTools.Method(type3, "UseStamina", new Type[2] { typeof(float), typeof(bool) }, (Type[])null); } } internal static bool IsOfficiallyFlying(CharacterMovement move) { return (Object)(object)move != (Object)null && move.balloonFloatMultiplier < 0f; } internal static bool IsParasolOpen(Character c) { if ((Object)(object)c == (Object)null || (Object)(object)c.data == (Object)null) { return false; } if (c.data.isGrounded) { return false; } Parasol heldParasol = GetHeldParasol(c); return (Object)(object)heldParasol != (Object)null && heldParasol.isOpen; } internal static Parasol GetHeldParasol(Character c) { if ((Object)(object)c == (Object)null || (Object)(object)c.data == (Object)null) { return null; } Item currentItem = c.data.currentItem; if ((Object)(object)currentItem == (Object)null) { return null; } Parasol val = ((Component)currentItem).GetComponent<Parasol>(); if ((Object)(object)val == (Object)null) { val = ((Component)currentItem).GetComponentInChildren<Parasol>(true); } return val; } internal static void SingleCrouchEdgeAction(CharacterMovement cm) { if (!HandshakeActive() || (Object)(object)cm == (Object)null) { return; } Character component = ((Component)cm).GetComponent<Character>(); if ((Object)(object)component == (Object)null || component.refs == null || !Object.op_Implicit((Object)(object)component.refs.view) || !component.refs.view.IsMine) { return; } CharacterInput input = component.input; if ((Object)(object)input == (Object)null) { return; } int viewID = component.refs.view.ViewID; bool flag = input.crouchIsPressed || input.crouchToggleWasPressed; bool value = false; s_crouchHeld.TryGetValue(viewID, out value); if (!value && flag) { Item val = (((Object)(object)component.data != (Object)null) ? component.data.currentItem : null); Action_Parasol val2 = null; Parasol val3 = null; if ((Object)(object)val != (Object)null) { val2 = ((Component)val).GetComponent<Action_Parasol>() ?? ((Component)val).GetComponentInChildren<Action_Parasol>(true); val3 = ((Component)val).GetComponent<Parasol>() ?? ((Component)val).GetComponentInChildren<Parasol>(true); } if ((Object)(object)val2 != (Object)null && (Object)(object)val2.parasol != (Object)null) { ((ItemActionBase)val2).RunAction(); } else if ((Object)(object)val3 != (Object)null) { val3.ToggleOpen(); } else { TryPopOneTiedBalloon(component); } } s_crouchHeld[viewID] = flag; } internal static bool TryPopOneTiedBalloon(Character c) { if ((Object)(object)c == (Object)null || c.refs == null) { return false; } CharacterBalloons balloons = c.refs.balloons; if ((Object)(object)balloons == (Object)null) { return false; } try { if (!(_fiTiedBalloons?.GetValue(balloons) is IList list) || list.Count == 0) { return false; } object obj = list[list.Count - 1]; _miPop?.Invoke(obj, null); return true; } catch (Exception ex) { EnhancedBalloonParasolControl instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("[EnhancedBalloonParasol] Pop failed: " + ex.Message)); } return false; } } }