Decompiled source of Enhanced Balloon and Parasol Control v1.0.0

tony4twentys-Enhanced Balloon and Parasol Control.dll

Decompiled a week ago
using 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;
		}
	}
}