Decompiled source of SpongePEAKOptimized v1.0.0

plugins/com.sponge.peakoptimized.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Peak.Network;
using Photon.Pun;
using Photon.Realtime;
using Photon.Voice;
using Photon.Voice.PUN;
using Photon.Voice.Unity;
using SpongePEAK.Core.Logging;
using SpongePEAK.Core.Patching;
using SpongePEAK.Core.Reflection;
using SpongePEAK.Lib.Events;
using SpongePEAK.Lib.Features;
using SpongePEAK.Lib.Net;
using SpongePEAK.Lib.Platform;
using SpongePEAK.Lib.Plugin;
using SpongePEAK.Optimized.Fixes;
using SpongePEAK.Optimized.Rejoin;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.SceneManagement;
using Zorro.Core;
using Zorro.Core.CLI;

[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("sponge")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) sponge")]
[assembly: AssemblyDescription("Bundled fixes and optimizations, each individually toggleable.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+ddcbf023fcb4c2065ad9b038910633cd4a3a93b0")]
[assembly: AssemblyProduct("SpongePEAKOptimized")]
[assembly: AssemblyTitle("com.sponge.peakoptimized")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 SpongePEAK.Optimized
{
	public static class Capabilities
	{
		public static ModHandshake Handshake { get; set; }

		public static bool HostSupportsUs
		{
			get
			{
				if (Handshake != null)
				{
					return Handshake.HostHasMod("com.sponge.peakoptimized");
				}
				return false;
			}
		}

		public static bool CanSendToHost
		{
			get
			{
				if (NetRoleInfo.IsClient)
				{
					return HostSupportsUs;
				}
				return false;
			}
		}

		public static bool DisconnectPersistenceActive
		{
			get
			{
				if (!NetRoleInfo.HasAuthority)
				{
					return HostSupportsUs;
				}
				return true;
			}
		}

		public static bool EveryoneSupportsUs
		{
			get
			{
				if (Handshake != null)
				{
					return Handshake.EveryoneHasMod("com.sponge.peakoptimized");
				}
				return false;
			}
		}
	}
	[BepInPlugin("com.sponge.peakoptimized", "SpongePEAKOptimized", "1.0.0")]
	public sealed class OptimizedPlugin : SpongePlugin
	{
		public const string Guid = "com.sponge.peakoptimized";

		private FeatureRegistry _registry;

		private AdaptiveNetworkFeature _adaptiveNetwork;

		private JoinArbiter _joinArbiter;

		private GameEvents _events;

		private ModHandshake _handshake;

		private LateJoinFixFeature _lateJoin;

		private ReconnectCatchupFeature _reconnectCatchup;

		private RejoinPositionFeature _rejoinPosition;

		private GhostPlayerFeature _ghostPlayer;

		private RopeShooterFixFeature _ropeShooterFix;

		private AudioFixFeature _audioFix;

		private DecorationCullingFeature _decorationCulling;

		private VisualQualityFeature _visualQuality;

		private TelemetryFeature _telemetry;

		private LobbyJoinFixFeature _lobbyJoinFix;

		protected override string DisplayName => "SpongePEAKOptimized";

		protected override void OnLoad()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			_registry = new FeatureRegistry(((BaseUnityPlugin)this).Config, ((SpongePlugin)this).Log);
			_joinArbiter = new JoinArbiter(((SpongePlugin)this).Log, (MonoBehaviour)(object)this);
			_events = new GameEvents(((SpongePlugin)this).Log);
			_handshake = new ModHandshake(((SpongePlugin)this).Log);
			_handshake.Start();
			Capabilities.Handshake = _handshake;
			_lateJoin = new LateJoinFixFeature();
			_lateJoin.Configure(((BaseUnityPlugin)this).Config, _joinArbiter, _events);
			_reconnectCatchup = new ReconnectCatchupFeature();
			_reconnectCatchup.Configure(((BaseUnityPlugin)this).Config, _joinArbiter);
			_rejoinPosition = new RejoinPositionFeature();
			_rejoinPosition.Configure(((BaseUnityPlugin)this).Config, _joinArbiter, _events);
			_ropeShooterFix = new RopeShooterFixFeature();
			_ghostPlayer = new GhostPlayerFeature();
			_ghostPlayer.Configure(((BaseUnityPlugin)this).Config, _events);
			_audioFix = new AudioFixFeature();
			_audioFix.Configure(((BaseUnityPlugin)this).Config);
			_lobbyJoinFix = new LobbyJoinFixFeature();
			PerformanceFeature performanceFeature = new PerformanceFeature();
			performanceFeature.Configure(((BaseUnityPlugin)this).Config);
			ZeroAllocFeature zeroAllocFeature = new ZeroAllocFeature();
			zeroAllocFeature.Configure(((BaseUnityPlugin)this).Config);
			DistanceCullingFeature distanceCullingFeature = new DistanceCullingFeature();
			distanceCullingFeature.Configure(((BaseUnityPlugin)this).Config);
			LightSampleCacheFeature lightSampleCacheFeature = new LightSampleCacheFeature();
			lightSampleCacheFeature.Configure(((BaseUnityPlugin)this).Config);
			PhysicsThrottleFeature physicsThrottleFeature = new PhysicsThrottleFeature();
			physicsThrottleFeature.Configure(((BaseUnityPlugin)this).Config);
			_decorationCulling = new DecorationCullingFeature();
			_decorationCulling.Configure(((BaseUnityPlugin)this).Config);
			_visualQuality = new VisualQualityFeature();
			_visualQuality.Configure(((BaseUnityPlugin)this).Config);
			ProximityGateFeature proximityGateFeature = new ProximityGateFeature();
			proximityGateFeature.Configure(((BaseUnityPlugin)this).Config);
			_telemetry = new TelemetryFeature();
			_telemetry.Configure(((BaseUnityPlugin)this).Config);
			PlatformTuningFeature platformTuningFeature = new PlatformTuningFeature();
			platformTuningFeature.Configure(((BaseUnityPlugin)this).Config);
			_adaptiveNetwork = new AdaptiveNetworkFeature();
			_adaptiveNetwork.Configure(((BaseUnityPlugin)this).Config);
			_registry.Register((ModFeature)(object)new VineClimbFixFeature());
			_registry.Register((ModFeature)(object)performanceFeature);
			_registry.Register((ModFeature)(object)zeroAllocFeature);
			_registry.Register((ModFeature)(object)distanceCullingFeature);
			_registry.Register((ModFeature)(object)_audioFix);
			_registry.Register((ModFeature)(object)new RejoinVoiceFixFeature());
			_registry.Register((ModFeature)(object)_lobbyJoinFix);
			_registry.Register((ModFeature)(object)new SoftDependencyFixFeature());
			_registry.Register((ModFeature)(object)_lateJoin);
			_registry.Register((ModFeature)(object)_rejoinPosition);
			_registry.Register((ModFeature)(object)_reconnectCatchup);
			_registry.Register((ModFeature)(object)new DisconnectedCarryFeature());
			_registry.Register((ModFeature)(object)_ropeShooterFix);
			_registry.Register((ModFeature)(object)_ghostPlayer);
			_registry.Register((ModFeature)(object)lightSampleCacheFeature);
			_registry.Register((ModFeature)(object)physicsThrottleFeature);
			_registry.Register((ModFeature)(object)_decorationCulling);
			_registry.Register((ModFeature)(object)new GrassAllocationFeature());
			_registry.Register((ModFeature)(object)new GrassCullingFeature());
			_registry.Register((ModFeature)(object)proximityGateFeature);
			_registry.Register((ModFeature)(object)new NullGuardFeature());
			_registry.Register((ModFeature)(object)_visualQuality);
			_registry.Register((ModFeature)(object)_telemetry);
			_registry.Register((ModFeature)(object)platformTuningFeature);
			_registry.Register((ModFeature)(object)_adaptiveNetwork);
			_registry.EnableAll(((SpongePlugin)this).Patches);
			if (_telemetry != null && _adaptiveNetwork != null)
			{
				_telemetry.Quality = _adaptiveNetwork.Quality;
			}
			if (AnyJoinFeatureActive())
			{
				_events.OnPlayerConnected((Action<Player>)OnPlayerConnected);
				if (PhotonNetwork.InRoom && PhotonNetwork.PlayerList != null)
				{
					_joinArbiter.SnapshotExistingPlayers(PhotonNetwork.PlayerList);
				}
			}
		}

		private bool AnyJoinFeatureActive()
		{
			if ((_lateJoin == null || !((ModFeature)_lateJoin).IsActive) && (_rejoinPosition == null || !((ModFeature)_rejoinPosition).IsActive))
			{
				if (_reconnectCatchup != null)
				{
					return ((ModFeature)_reconnectCatchup).IsActive;
				}
				return false;
			}
			return true;
		}

		private void OnPlayerConnected(Player player)
		{
			if (!NetRoleInfo.HasAuthority)
			{
				_joinArbiter.MarkSeen(player);
			}
			else
			{
				_joinArbiter.HandleJoin(player);
			}
		}

		private void Update()
		{
			if (_audioFix != null && ((ModFeature)_audioFix).IsActive)
			{
				_audioFix.Tick();
			}
			if (_decorationCulling != null && ((ModFeature)_decorationCulling).IsActive)
			{
				_decorationCulling.Tick();
			}
			if (_telemetry != null && ((ModFeature)_telemetry).IsActive)
			{
				_telemetry.Tick();
			}
			if (_adaptiveNetwork != null && ((ModFeature)_adaptiveNetwork).IsActive)
			{
				_adaptiveNetwork.Tick();
			}
		}

		private void OnGUI()
		{
			if (_telemetry != null && ((ModFeature)_telemetry).IsActive)
			{
				_telemetry.Draw();
			}
		}

		protected override void OnUnload()
		{
			ModHandshake handshake = _handshake;
			if (handshake != null)
			{
				handshake.Stop();
			}
			Capabilities.Handshake = null;
			GameEvents events = _events;
			if (events != null)
			{
				events.UnsubscribeAll();
			}
			_lateJoin?.Detach();
			_reconnectCatchup?.Detach();
			_rejoinPosition?.Detach();
			_audioFix?.RestoreAll();
			_adaptiveNetwork?.Cleanup();
			_decorationCulling?.Cleanup();
			_visualQuality?.Cleanup();
			LightSampleCacheFeature.Clear();
			PerformanceFeature.Reset();
			DisconnectedPlayerRegistry.Reset();
			_joinArbiter?.Reset();
		}
	}
}
namespace SpongePEAK.Optimized.Rejoin
{
	public static class DisconnectedPlayerRegistry
	{
		public sealed class DisconnectedState
		{
			public string PlayerKey;

			public string CharacterName;

			public Vector3 LastPosition;

			public float FogHeightAtDisconnect;

			public Character Character;

			public float DisconnectedAt;

			public bool DiedWhileDisconnected;
		}

		private static readonly Dictionary<string, DisconnectedState> States = new Dictionary<string, DisconnectedState>();

		private static readonly HashSet<Character> AwayCharacters = new HashSet<Character>();

		public static IEnumerable<DisconnectedState> All => States.Values;

		public static string GetStableKey(Player player)
		{
			return PlayerUtil.GetStableKey(player);
		}

		public static DisconnectedState Record(Player player, Character character, float fogHeight, ModLog log)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			string stableKey = GetStableKey(player);
			if (string.IsNullOrEmpty(stableKey))
			{
				return null;
			}
			DisconnectedState disconnectedState = new DisconnectedState
			{
				PlayerKey = stableKey,
				CharacterName = (((Object)(object)character != (Object)null) ? character.characterName : player.NickName),
				LastPosition = (((Object)(object)character != (Object)null) ? character.Center : Vector3.zero),
				FogHeightAtDisconnect = fogHeight,
				Character = character,
				DisconnectedAt = Time.realtimeSinceStartup
			};
			States[stableKey] = disconnectedState;
			if ((Object)(object)character != (Object)null)
			{
				AwayCharacters.Add(character);
			}
			if (log != null)
			{
				log.Info((object)$"Recorded disconnect for '{disconnectedState.CharacterName}' at {disconnectedState.LastPosition}.");
			}
			return disconnectedState;
		}

		public static DisconnectedState Find(Player player)
		{
			string stableKey = GetStableKey(player);
			if (string.IsNullOrEmpty(stableKey))
			{
				return null;
			}
			if (!States.TryGetValue(stableKey, out var value))
			{
				return null;
			}
			return value;
		}

		public static void Clear(Player player)
		{
			string stableKey = GetStableKey(player);
			if (!string.IsNullOrEmpty(stableKey) && States.TryGetValue(stableKey, out var value))
			{
				if ((Object)(object)value.Character != (Object)null)
				{
					AwayCharacters.Remove(value.Character);
				}
				States.Remove(stableKey);
			}
		}

		public static bool IsAway(Character character)
		{
			if ((Object)(object)character != (Object)null)
			{
				return AwayCharacters.Contains(character);
			}
			return false;
		}

		public static void Reset()
		{
			States.Clear();
			AwayCharacters.Clear();
		}
	}
	public enum JoinKind
	{
		None,
		LateJoin,
		RestorePosition,
		CatchUp
	}
	public sealed class JoinArbiter
	{
		private readonly ModLog _log;

		private readonly MonoBehaviour _coroutineHost;

		private readonly HashSet<string> _knownPlayers = new HashSet<string>();

		private readonly HashSet<int> _inFlight = new HashSet<int>();

		public Func<Player, Character, IEnumerator> LateJoinHandler;

		public Func<Player, Character, IEnumerator> RestoreHandler;

		public Func<Player, Character, IEnumerator> CatchUpHandler;

		public Func<Player, bool> CanRestorePosition;

		public JoinArbiter(ModLog log, MonoBehaviour coroutineHost)
		{
			_log = log;
			_coroutineHost = coroutineHost;
		}

		public void SnapshotExistingPlayers(IEnumerable<Player> players)
		{
			if (players == null)
			{
				return;
			}
			foreach (Player player in players)
			{
				string stableKey = PlayerUtil.GetStableKey(player);
				if (!string.IsNullOrEmpty(stableKey))
				{
					_knownPlayers.Add(stableKey);
				}
			}
			ModLog log = _log;
			if (log != null)
			{
				log.Debug((object)$"Join arbiter snapshot: {_knownPlayers.Count} player(s) already present.");
			}
		}

		public void MarkSeen(Player player)
		{
			string stableKey = PlayerUtil.GetStableKey(player);
			if (!string.IsNullOrEmpty(stableKey))
			{
				_knownPlayers.Add(stableKey);
			}
		}

		public bool HasSeen(Player player)
		{
			string stableKey = PlayerUtil.GetStableKey(player);
			if (!string.IsNullOrEmpty(stableKey))
			{
				return _knownPlayers.Contains(stableKey);
			}
			return false;
		}

		public JoinKind Classify(Player player)
		{
			if (player == null || PlayerUtil.InAirport)
			{
				return JoinKind.None;
			}
			if (!HasSeen(player))
			{
				if (LateJoinHandler == null)
				{
					return JoinKind.None;
				}
				return JoinKind.LateJoin;
			}
			if (RestoreHandler != null && CanRestorePosition != null && CanRestorePosition(player))
			{
				return JoinKind.RestorePosition;
			}
			if (CatchUpHandler == null)
			{
				return JoinKind.None;
			}
			return JoinKind.CatchUp;
		}

		public void HandleJoin(Player player)
		{
			if (player == null)
			{
				return;
			}
			if (!_inFlight.Add(player.ActorNumber))
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Debug((object)("Already handling '" + player.NickName + "'; ignoring duplicate join event."));
				}
				return;
			}
			JoinKind joinKind = Classify(player);
			MarkSeen(player);
			if (joinKind == JoinKind.None)
			{
				_inFlight.Remove(player.ActorNumber);
				return;
			}
			ModLog log2 = _log;
			if (log2 != null)
			{
				log2.Info((object)$"Join classified for '{player.NickName}': {joinKind}.");
			}
			if ((Object)(object)_coroutineHost != (Object)null)
			{
				_coroutineHost.StartCoroutine(Dispatch(player, joinKind));
			}
			else
			{
				_inFlight.Remove(player.ActorNumber);
			}
		}

		private IEnumerator Dispatch(Player player, JoinKind kind)
		{
			Character character = null;
			yield return PlayerUtil.WaitForCharacter(player, (Action<Character>)delegate(Character c)
			{
				character = c;
			}, 30f, 0.1f);
			if ((Object)(object)character == (Object)null)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Warning((object)("'" + player.NickName + "' never produced a character; nothing to do."));
				}
				_inFlight.Remove(player.ActorNumber);
				yield break;
			}
			Func<Player, Character, IEnumerator> func = kind switch
			{
				JoinKind.LateJoin => LateJoinHandler, 
				JoinKind.RestorePosition => RestoreHandler, 
				JoinKind.CatchUp => CatchUpHandler, 
				_ => null, 
			};
			if (func != null)
			{
				yield return func(player, character);
			}
			_inFlight.Remove(player.ActorNumber);
		}

		public void Reset()
		{
			_knownPlayers.Clear();
			_inFlight.Clear();
		}
	}
}
namespace SpongePEAK.Optimized.Fixes
{
	public sealed class AdaptiveNetworkFeature : ModFeature
	{
		private const int VanillaSendRate = 30;

		private const int VanillaSerializationRate = 30;

		public const string RatePropertyKey = "sponge.netrate";

		private ConfigEntry<bool> _adapt;

		private ConfigEntry<float> _interval;

		private ConfigEntry<int> _floor;

		private ConfigEntry<bool> _report;

		private LinkQuality _quality;

		private LinkReporter _reporter;

		private ModLog _log;

		private float _timer;

		private int _appliedRate = 30;

		private const int RateStep = 5;

		public override string Name => "AdaptiveNetwork";

		public override string Description => "Measures each player's connection and lowers the room's send rate when someone is struggling, so weak connections stop rubber-banding. Host decides, clients match.";

		public override string Replaces => "";

		public override string Section => "Network";

		public LinkQuality Quality => _quality;

		public void Configure(ConfigFile config)
		{
			_adapt = config.Bind<bool>("Network", "AdaptSendRate", true, "Lower the room's network send rate when a player's connection cannot keep up. Host decides; clients follow the host so everyone's interpolation matches. Off means always use the vanilla 30Hz.");
			_interval = config.Bind<float>("Network", "SampleIntervalSeconds", 5f, "How often to read connection quality. This costs no packets: it reads counters Photon already maintains, so a short interval is cheap and reacts sooner.");
			_floor = config.Bind<int>("Network", "MinimumSerializationRate", 15, "Lowest serialisation rate the host may drop to, in Hz. Vanilla is 30. Below about 15 the game's own interpolation starts to look obviously soft.");
			_report = config.Bind<bool>("Network", "LogConnectionQuality", true, "Log connection tier changes and rate adjustments. Useful for diagnosing whose connection is actually causing trouble.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			_log = log;
			_quality = new LinkQuality(log);
			_reporter = new LinkReporter(_quality, log);
			_reporter.Start();
			log.Info((object)"Adaptive networking active: measuring connection quality from Photon telemetry.");
		}

		public void Tick()
		{
			if (!((ModFeature)this).IsActive || _quality == null || !PhotonNetwork.InRoom)
			{
				return;
			}
			_timer += Time.unscaledDeltaTime;
			if (_timer < Mathf.Max(1f, _interval?.Value ?? 5f))
			{
				return;
			}
			_timer = 0f;
			_quality.Sample();
			ConfigEntry<bool> adapt = _adapt;
			if (adapt == null || adapt.Value)
			{
				if (PhotonNetwork.IsMasterClient)
				{
					DecideAsHost();
				}
				else
				{
					FollowHost();
				}
			}
		}

		private int PublishedRate()
		{
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				Hashtable val = ((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null);
				if (val != null && ((Dictionary<object, object>)(object)val).TryGetValue((object)"sponge.netrate", out object value) && value is byte b)
				{
					return Mathf.Clamp((int)b, 10, 30);
				}
			}
			catch (Exception)
			{
			}
			return 30;
		}

		private void DecideAsHost()
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected I4, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			if (!Capabilities.EveryoneSupportsUs)
			{
				if (_appliedRate == 30 && PublishedRate() == 30)
				{
					return;
				}
				ApplyRate(30);
				PublishRate(30);
				ConfigEntry<bool> report = _report;
				if (report == null || report.Value)
				{
					ModLog log = _log;
					if (log != null)
					{
						log.Info((object)"A player without the mod is present; returned to vanilla 30Hz so nobody is left interpolating against the wrong rate.");
					}
				}
				return;
			}
			int num = PublishedRate();
			if (num != _appliedRate)
			{
				ApplyRate(num);
				ConfigEntry<bool> report2 = _report;
				if (report2 == null || report2.Value)
				{
					ModLog log2 = _log;
					if (log2 != null)
					{
						log2.Info((object)$"Adopted the room's current send rate: {num}Hz.");
					}
				}
			}
			LinkTier val = LinkReporter.WorstReported();
			int num2 = (val - 2) switch
			{
				2 => 15, 
				1 => 20, 
				0 => 25, 
				_ => 30, 
			};
			int num3 = Mathf.Clamp(_floor?.Value ?? 15, 10, 30);
			num2 = Mathf.Clamp(num2, num3, 30);
			if (num2 == _appliedRate)
			{
				return;
			}
			int num4 = StepToward(_appliedRate, num2);
			ApplyRate(num4);
			PublishRate(num4);
			ConfigEntry<bool> report3 = _report;
			if (report3 == null || report3.Value)
			{
				ModLog log3 = _log;
				if (log3 != null)
				{
					log3.Info((object)($"Room send rate -> {num4}Hz (worst reported connection: {val}). " + "Clients will match."));
				}
			}
		}

		internal static int StepToward(int current, int target)
		{
			if (target > current)
			{
				return Math.Min(current + 5, target);
			}
			return Math.Max(current - 5, target);
		}

		private void FollowHost()
		{
			try
			{
				int num = PublishedRate();
				if (num == _appliedRate)
				{
					return;
				}
				ApplyRate(num);
				ConfigEntry<bool> report = _report;
				if (report == null || report.Value)
				{
					ModLog log = _log;
					if (log != null)
					{
						log.Info((object)$"Matched host's send rate: {num}Hz.");
					}
				}
			}
			catch (Exception ex)
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Exception("Could not read the host's send rate", ex);
				}
			}
		}

		private void ApplyRate(int rate)
		{
			try
			{
				PhotonNetwork.SerializationRate = rate;
				PhotonNetwork.SendRate = Mathf.Max(rate, 15);
				_appliedRate = rate;
			}
			catch (Exception ex)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Exception("Could not apply the network rate", ex);
				}
			}
		}

		private void PublishRate(int rate)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0021: Expected O, but got Unknown
			if (!PhotonNetwork.IsMasterClient)
			{
				return;
			}
			try
			{
				Hashtable val = new Hashtable();
				((Dictionary<object, object>)val).Add((object)"sponge.netrate", (object)(byte)rate);
				Hashtable val2 = val;
				PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
			}
			catch (Exception ex)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Exception("Could not publish the network rate", ex);
				}
			}
		}

		public void Cleanup()
		{
			LinkReporter reporter = _reporter;
			if (reporter != null)
			{
				reporter.Stop();
			}
			try
			{
				PhotonNetwork.SerializationRate = 30;
				PhotonNetwork.SendRate = 30;
			}
			catch (Exception)
			{
			}
		}
	}
	public sealed class AudioFixFeature : ModFeature
	{
		private readonly struct PausedLoop
		{
			public readonly AudioSource Source;

			public readonly float Time;

			public PausedLoop(AudioSource source, float time)
			{
				Source = source;
				Time = time;
			}
		}

		private const string VoiceClipName = "UnityAudioOut";

		private const float SilenceThreshold = 0.001f;

		private const float ResumeThreshold = 0.01f;

		private const int VoicePriority = 0;

		private const int GlobalPriority = 20;

		private const float NearPriority = 80f;

		private const float FarPriority = 220f;

		private const float GlobalSpatialBlendThreshold = 0.05f;

		private ModLog _log;

		private ConfigEntry<float> _scanInterval;

		private readonly Dictionary<int, PausedLoop> _pausedLoops = new Dictionary<int, PausedLoop>();

		private readonly List<int> _scratch = new List<int>();

		private float _nextScanAt;

		private int _totalParked;

		private int _totalResumed;

		public override string Name => "AudioFix";

		public override string Description => "Stops audio dropouts by parking silent looping sounds and prioritising voice and nearby audio.";

		public override string Replaces => "PatchNote-PeakAudioFix";

		internal void Configure(ConfigFile config)
		{
			_scanInterval = config.Bind<float>("AudioFix", "ScanInterval", 0.5f, "Seconds between audio source scans. Lower reacts faster but costs more CPU; FindObjectsByType is not cheap.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
		}

		internal void Tick()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!(realtimeSinceStartup < _nextScanAt))
			{
				_nextScanAt = realtimeSinceStartup + (_scanInterval?.Value ?? 0.5f);
				Camera main = Camera.main;
				Vector3 listenerPosition = (((Object)(object)main != (Object)null) ? ((Component)main).transform.position : Vector3.zero);
				ScanForSilentLoops(realtimeSinceStartup, listenerPosition);
				ResumeAudibleSources(realtimeSinceStartup);
			}
		}

		private void ScanForSilentLoops(float now, Vector3 listenerPosition)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			AudioSource[] array = Object.FindObjectsByType<AudioSource>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (AudioSource val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.clip == (Object)null)
				{
					continue;
				}
				int instanceID = ((Object)val).GetInstanceID();
				if (!_pausedLoops.ContainsKey(instanceID))
				{
					ApplyPriorityPolicy(val, listenerPosition);
					if (val.isPlaying && IsParkCandidate(val) && IsSilent(val))
					{
						Park(val, instanceID, now);
					}
				}
			}
		}

		private void ResumeAudibleSources(float now)
		{
			if (_pausedLoops.Count == 0)
			{
				return;
			}
			_scratch.Clear();
			foreach (int key in _pausedLoops.Keys)
			{
				_scratch.Add(key);
			}
			foreach (int item in _scratch)
			{
				if (_pausedLoops.TryGetValue(item, out var value))
				{
					AudioSource source = value.Source;
					if ((Object)(object)source == (Object)null)
					{
						_pausedLoops.Remove(item);
					}
					else if (ShouldResume(source))
					{
						Resume(source, value, item);
					}
				}
			}
		}

		private void Park(AudioSource source, int id, float now)
		{
			_pausedLoops[id] = new PausedLoop(source, source.time);
			source.Pause();
			_totalParked++;
		}

		private void Resume(AudioSource source, PausedLoop parked, int id)
		{
			_pausedLoops.Remove(id);
			if (!source.isPlaying)
			{
				AudioClip clip = source.clip;
				if ((Object)(object)clip != (Object)null && parked.Time > 0f && parked.Time < clip.length)
				{
					source.time = parked.Time;
				}
				source.UnPause();
			}
			_totalResumed++;
		}

		private static bool IsParkCandidate(AudioSource source)
		{
			if (!source.loop || (Object)(object)source.clip == (Object)null)
			{
				return false;
			}
			return ((Object)source.clip).name != "UnityAudioOut";
		}

		private static bool IsSilent(AudioSource source)
		{
			return source.volume <= 0.001f;
		}

		private static bool ShouldResume(AudioSource source)
		{
			if (!((Behaviour)source).enabled || !((Component)source).gameObject.activeInHierarchy || source.mute)
			{
				return false;
			}
			return source.volume >= 0.01f;
		}

		private void ApplyPriorityPolicy(AudioSource source, Vector3 listenerPosition)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			int targetPriority = GetTargetPriority(source, listenerPosition);
			if (source.priority != targetPriority)
			{
				source.priority = targetPriority;
			}
		}

		private static int GetTargetPriority(AudioSource source, Vector3 listenerPosition)
		{
			//IL_0026: 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)
			if (IsVoiceSource(source))
			{
				return 0;
			}
			if (IsGlobalSource(source))
			{
				return 20;
			}
			float num = Mathf.Max(1f, source.maxDistance);
			float num2 = Mathf.Clamp01(Vector3.Distance(listenerPosition, ((Component)source).transform.position) / num);
			return Mathf.RoundToInt(Mathf.Lerp(80f, 220f, num2));
		}

		private static bool IsVoiceSource(AudioSource source)
		{
			if ((Object)(object)source.clip != (Object)null)
			{
				return ((Object)source.clip).name == "UnityAudioOut";
			}
			return false;
		}

		private static bool IsGlobalSource(AudioSource source)
		{
			if (source.spatialBlend <= 0.05f)
			{
				return true;
			}
			if ((Object)(object)source.clip != (Object)null && ((Object)source.clip).name.StartsWith("Music", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return HasAncestorNamed(((Component)source).transform, "Music");
		}

		private static bool HasAncestorNamed(Transform transform, string name)
		{
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				if (string.Equals(((Object)val).name, name, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		internal void RestoreAll()
		{
			foreach (KeyValuePair<int, PausedLoop> pausedLoop in _pausedLoops)
			{
				AudioSource source = pausedLoop.Value.Source;
				if ((Object)(object)source != (Object)null && !source.isPlaying)
				{
					source.UnPause();
				}
			}
			_pausedLoops.Clear();
			ModLog log = _log;
			if (log != null)
			{
				log.Debug((object)$"Audio fix summary: parked={_totalParked}, resumed={_totalResumed}.");
			}
		}
	}
	public sealed class DecorationCullingFeature : ModFeature
	{
		private ConfigEntry<float> _distance;

		private ConfigEntry<float> _maxExtent;

		private ConfigEntry<int> _perFrame;

		public override string Name => "DecorationCulling";

		public override string Description => "Hides small decorative meshes beyond a distance, spreading the work across frames.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Performance";

		public void Configure(ConfigFile config)
		{
			_distance = config.Bind<float>("Performance", "DecorationCullDistance", 90f, "Distance beyond which small decorations stop being drawn.");
			_maxExtent = config.Bind<float>("Performance", "DecorationMaxExtent", 1.5f, "Largest bounds extent, in metres, that counts as a decoration. Anything bigger is left alone, so climbable geometry is never hidden.");
			_perFrame = config.Bind<int>("Performance", "DecorationChecksPerFrame", 64, "How many decorations to test each frame. Lower spreads the cost further.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			DecorationRegistry.Configure(_distance?.Value ?? 90f, _maxExtent?.Value ?? 1.5f, _perFrame?.Value ?? 64, log);
			MethodInfo methodInfo = AccessTools.Method(typeof(DecorationRegistry), "RegisterFrom", (Type[])null, (Type[])null);
			(Type, string)[] array = new(Type, string)[4]
			{
				(typeof(RockSpawner), "Go"),
				(typeof(DecorSpawner), "ExecuteImmediately"),
				(typeof(DesertRockSpawner), "ExecuteImmediately"),
				(typeof(PropSpawner), "ExecuteImmediately")
			};
			int num = 0;
			(Type, string)[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				(Type, string) tuple = array2[i];
				Type item = tuple.Item1;
				string item2 = tuple.Item2;
				MethodInfo methodInfo2 = AccessTools.Method(item, item2, (Type[])null, (Type[])null);
				if (methodInfo2 == null)
				{
					log.Warning((object)(item.Name + "." + item2 + " not found; its decorations will not be culled."));
				}
				else if (patches.PatchMethod((MethodBase)methodInfo2, (MethodInfo)null, methodInfo, (MethodInfo)null, (MethodInfo)null))
				{
					num++;
				}
			}
			if (num == 0)
			{
				((ModFeature)this).Fail("no decoration spawners could be patched");
			}
			else
			{
				log.Info((object)$"Decoration culling active on {num} of {array.Length} spawners.");
			}
		}

		public void Tick()
		{
			DecorationRegistry.Tick();
		}

		public void Cleanup()
		{
			DecorationRegistry.RestoreAll();
		}
	}
	internal static class DecorationRegistry
	{
		private struct Entry
		{
			public MeshRenderer Renderer;

			public Vector3 Position;

			public bool Visible;
		}

		private static readonly List<Entry> Entries = new List<Entry>(1024);

		private static readonly HashSet<int> Known = new HashSet<int>();

		private static float _distanceSqr = 8100f;

		private static float _maxExtentSqr = 2.25f;

		private static int _perFrame = 64;

		private static int _cursor;

		private static ModLog _log;

		internal static int CulledCount { get; private set; }

		internal static int TrackedCount => Entries.Count;

		internal static void Configure(float distance, float maxExtent, int perFrame, ModLog log)
		{
			_distanceSqr = distance * distance;
			_maxExtentSqr = maxExtent * maxExtent;
			_perFrame = Mathf.Max(1, perFrame);
			_log = log;
		}

		internal static void RegisterFrom(Component __instance)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: 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_0082: 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)
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			MeshRenderer[] componentsInChildren = __instance.GetComponentsInChildren<MeshRenderer>(true);
			foreach (MeshRenderer val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				int instanceID = ((Object)val).GetInstanceID();
				if (!Known.Contains(instanceID))
				{
					Bounds bounds = ((Renderer)val).bounds;
					Vector3 extents = ((Bounds)(ref bounds)).extents;
					if (!(((Vector3)(ref extents)).sqrMagnitude > _maxExtentSqr))
					{
						Known.Add(instanceID);
						Entries.Add(new Entry
						{
							Renderer = val,
							Position = ((Component)val).transform.position,
							Visible = true
						});
					}
				}
			}
		}

		internal static void Tick()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_0096: Unknown result type (might be due to invalid IL or missing references)
			if (Entries.Count == 0)
			{
				return;
			}
			Vector3? val = Viewer();
			if (!val.HasValue)
			{
				return;
			}
			Vector3 value = val.Value;
			int num = Mathf.Min(_perFrame, Entries.Count);
			for (int i = 0; i < num; i++)
			{
				if (_cursor >= Entries.Count)
				{
					_cursor = 0;
				}
				Entry value2 = Entries[_cursor];
				if ((Object)(object)value2.Renderer == (Object)null)
				{
					Entries.RemoveAt(_cursor);
					continue;
				}
				Vector3 val2 = value2.Position - value;
				bool flag = ((Vector3)(ref val2)).sqrMagnitude <= _distanceSqr;
				if (flag != value2.Visible)
				{
					((Renderer)value2.Renderer).enabled = flag;
					value2.Visible = flag;
					CulledCount += ((!flag) ? 1 : (-1));
					Entries[_cursor] = value2;
				}
				_cursor++;
			}
		}

		private static Vector3? Viewer()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			Character observedCharacter = Character.observedCharacter;
			if ((Object)(object)observedCharacter != (Object)null)
			{
				return observedCharacter.Center;
			}
			MainCamera instance = MainCamera.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return ((Component)instance).transform.position;
			}
			return null;
		}

		internal static void RestoreAll()
		{
			foreach (Entry entry in Entries)
			{
				if ((Object)(object)entry.Renderer != (Object)null)
				{
					((Renderer)entry.Renderer).enabled = true;
				}
			}
			Entries.Clear();
			Known.Clear();
			_cursor = 0;
			CulledCount = 0;
			ModLog log = _log;
			if (log != null)
			{
				log.Debug((object)"Decoration culling reset.");
			}
		}
	}
	public sealed class DisconnectedCarryFeature : ModFeature
	{
		public override string Name => "CarryDisconnectedPlayers";

		public override string Description => "Lets you pick up and carry teammates who disconnected while alive, and marks them (Disconnected). Ghosts cannot be carried.";

		public override string Replaces => "(new behaviour)";

		public override string Section => "Rejoin";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			if (!patches.PatchType(typeof(DisconnectedCarryPatch)))
			{
				((ModFeature)this).Fail("could not apply DisconnectedCarryPatch");
			}
			if (!patches.PatchType(typeof(DisconnectedNamePatch)))
			{
				((ModFeature)this).Fail("could not apply DisconnectedNamePatch");
			}
		}
	}
	[HarmonyPatch(typeof(CharacterInteractible), "CanBeCarried")]
	internal static class DisconnectedCarryPatch
	{
		[HarmonyPostfix]
		private static void Postfix(CharacterInteractible __instance, ref bool __result)
		{
			if (!__result)
			{
				Character val = __instance?.character;
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.data == (Object)null) && DisconnectedPlayerRegistry.IsAway(val) && !val.IsGhost && !val.data.dead && !val.isBot && !((Object)(object)val.data.carrier != (Object)null))
				{
					__result = true;
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterInteractible), "GetName")]
	internal static class DisconnectedNamePatch
	{
		[HarmonyPostfix]
		private static void Postfix(CharacterInteractible __instance, ref string __result)
		{
			Character val = __instance?.character;
			if (!((Object)(object)val == (Object)null) && DisconnectedPlayerRegistry.IsAway(val))
			{
				__result += " (Disconnected)";
			}
		}
	}
	public sealed class DistanceCullingFeature : ModFeature
	{
		private const float CappedUpdateRateHz = 30f;

		internal static ConfigEntry<bool> CullDynamicBones;

		internal static ConfigEntry<float> DynamicBoneDistance;

		internal static ConfigEntry<bool> CapUpdateRate;

		public override string Name => "DistanceCulling";

		public override string Description => "Skips dynamic-bone secondary motion beyond a distance from the camera, and caps its update rate.";

		public override string Replaces => "reis637-PeakOptimizer (partial)";

		public override string Section => "Performance";

		internal static float CappedRate => 30f;

		internal void Configure(ConfigFile config)
		{
			CullDynamicBones = config.Bind<bool>("Performance", "CullDistantDynamicBones", true, "Skips dynamic-bone updates beyond DynamicBoneDistance from the camera. Purely visual.");
			DynamicBoneDistance = config.Bind<float>("Performance", "DynamicBoneDistance", 40f, "Metres from the camera beyond which dynamic bones stop updating. Lower is faster; too low shows stiff foliage nearby.");
			CapUpdateRate = config.Bind<bool>("Performance", "CapDynamicBoneUpdateRate", true, $"Caps dynamic-bone update rate at {30f} Hz instead of the default 60.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			if (!patches.PatchType(typeof(DynamicBoneCullPatch)))
			{
				((ModFeature)this).Fail("could not apply DynamicBoneCullPatch");
			}
		}
	}
	[HarmonyPatch(typeof(DynamicBone), "UpdateDynamicBones")]
	internal static class DynamicBoneCullPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(DynamicBone __instance)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: 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)
			if (DistanceCullingFeature.CullDynamicBones == null)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.m_Root == (Object)null)
			{
				return true;
			}
			if (DistanceCullingFeature.CapUpdateRate.Value && __instance.m_UpdateRate > DistanceCullingFeature.CappedRate)
			{
				__instance.m_UpdateRate = DistanceCullingFeature.CappedRate;
			}
			if (!DistanceCullingFeature.CullDynamicBones.Value)
			{
				return true;
			}
			MainCamera instance = MainCamera.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return true;
			}
			float value = DistanceCullingFeature.DynamicBoneDistance.Value;
			float num = value * value;
			Vector3 val = ((Component)instance).transform.position - ((Component)__instance).transform.position;
			return ((Vector3)(ref val)).sqrMagnitude <= num;
		}
	}
	public sealed class GhostPlayerFeature : ModFeature
	{
		internal static ModLog Log;

		internal static ConfigEntry<int> PlayerTtl;

		private ModLog _log;

		private GameEvents _events;

		private bool _reportedState;

		public override string Name => "KeepDisconnectedPlayersInWorld";

		public override string Description => "Keeps a disconnected player's character alive and simulated until they rejoin, die, or the timeout expires. Room settings are applied when you host; every client tracks who is away.";

		public override string Replaces => "(new behaviour)";

		public override string Section => "Rejoin";

		public override bool DefaultEnabled => true;

		internal void Configure(ConfigFile config, GameEvents events)
		{
			_events = events;
			PlayerTtl = config.Bind<int>("Rejoin", "DisconnectedPlayerTimeoutSeconds", 300, "How long a disconnected player's character stays in the world before Photon reclaims it. They occupy a player slot for this whole period, so keep it in line with your player cap. Only applies to lobbies you host.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			Log = log;
			if (!patches.PatchType(typeof(RoomOptionsPlayerTtlPatch)))
			{
				((ModFeature)this).Fail("could not apply RoomOptionsPlayerTtlPatch");
			}
			_events.OnCharacterOwnerDisconnected((Action<Character>)OnCharacterOwnerDisconnected);
			_events.OnCharacterDied((Action<Character>)OnCharacterDied);
			_events.OnPlayerConnected((Action<Player>)delegate
			{
				ReportPersistenceStateOnce();
			});
		}

		private void ReportPersistenceStateOnce()
		{
			if (!_reportedState)
			{
				_reportedState = true;
				LogRoleState(_log);
			}
		}

		private void LogRoleState(ModLog log)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0008: Invalid comparison between Unknown and I4
			//IL_000a: 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_000e: Invalid comparison between Unknown and I4
			NetRole current = NetRoleInfo.Current;
			if ((int)current != 1)
			{
				if (current - 2 <= 1)
				{
					if (log != null)
					{
						log.Info((object)"Disconnected-player persistence: active (we host, so every client sees it).");
					}
				}
				else if (log != null)
				{
					log.Info((object)"Disconnected-player persistence: waiting until we are in a room.");
				}
			}
			else if (Capabilities.HostSupportsUs)
			{
				if (log != null)
				{
					log.Info((object)"Disconnected-player persistence: active (the host is running this mod).");
				}
			}
			else if (log != null)
			{
				log.Info((object)"Disconnected-player persistence: inactive. The host is not running this mod, so disconnected characters are removed by Photon. Labels and carrying still work for anyone who does persist.");
			}
		}

		private void OnCharacterOwnerDisconnected(Character character)
		{
			if (!((Object)(object)character == (Object)null))
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Info((object)("'" + character.characterName + "' disconnected; their character stays in the world."));
				}
			}
		}

		private void OnCharacterDied(Character character)
		{
			if ((Object)(object)character == (Object)null || !DisconnectedPlayerRegistry.IsAway(character))
			{
				return;
			}
			foreach (DisconnectedPlayerRegistry.DisconnectedState item in DisconnectedPlayerRegistry.All)
			{
				if ((Object)(object)item.Character == (Object)(object)character)
				{
					item.DiedWhileDisconnected = true;
					ModLog log = _log;
					if (log != null)
					{
						log.Info((object)("'" + item.CharacterName + "' died while disconnected; they are a ghost now and cannot be carried."));
					}
					break;
				}
			}
		}
	}
	[HarmonyPatch(typeof(NetworkingUtilities), "HostRoomOptions")]
	internal static class RoomOptionsPlayerTtlPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ref RoomOptions __result)
		{
			if (__result != null && GhostPlayerFeature.PlayerTtl != null)
			{
				__result.PlayerTtl = GhostPlayerFeature.PlayerTtl.Value * 1000;
				__result.CleanupCacheOnLeave = false;
				ModLog log = GhostPlayerFeature.Log;
				if (log != null)
				{
					log.Info((object)$"Hosting with disconnected-player persistence: PlayerTtl={__result.PlayerTtl}ms.");
				}
			}
		}
	}
	public sealed class GrassAllocationFeature : ModFeature
	{
		public override string Name => "GrassAllocations";

		public override string Description => "Reuses one MaterialPropertyBlock for grass instead of allocating a new one every frame.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Performance";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(GrassRenderer), "Render", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((ModFeature)this).Fail("GrassRenderer.Render not found");
				return;
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(GrassAllocations), "Transpiler", (Type[])null, (Type[])null);
			if (!patches.PatchMethod((MethodBase)methodInfo, (MethodInfo)null, (MethodInfo)null, methodInfo2, (MethodInfo)null))
			{
				((ModFeature)this).Fail("could not transpile GrassRenderer.Render");
			}
			else
			{
				log.Info((object)"Grass property block reuse active.");
			}
		}
	}
	internal static class GrassAllocations
	{
		private static readonly MaterialPropertyBlock Shared = new MaterialPropertyBlock();

		internal static MaterialPropertyBlock Get()
		{
			return Shared;
		}

		internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			ConstructorInfo constructorInfo = AccessTools.Constructor(typeof(MaterialPropertyBlock), (Type[])null, false);
			MethodInfo operand = AccessTools.Method(typeof(GrassAllocations), "Get", (Type[])null, (Type[])null);
			bool flag = false;
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i].opcode == OpCodes.Newobj && list[i].operand is ConstructorInfo constructorInfo2 && constructorInfo2 == constructorInfo)
				{
					list[i].opcode = OpCodes.Call;
					list[i].operand = operand;
					flag = true;
				}
			}
			if (!flag)
			{
				return instructions;
			}
			return list;
		}
	}
	public sealed class GrassCullingFeature : ModFeature
	{
		public override string Name => "GrassFrustumCulling";

		public override string Description => "Skips grass chunks outside the camera frustum. Grass casts no shadows, so this cannot change what you see.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Performance";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(GrassChunking), "ShouldDrawChunk", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((ModFeature)this).Fail("GrassChunking.ShouldDrawChunk not found");
			}
			else if (!patches.PatchMethod((MethodBase)methodInfo, (MethodInfo)null, AccessTools.Method(typeof(GrassCulling), "Postfix", (Type[])null, (Type[])null), (MethodInfo)null, (MethodInfo)null))
			{
				((ModFeature)this).Fail("could not patch GrassChunking.ShouldDrawChunk");
			}
			else
			{
				log.Info((object)"Grass frustum culling active.");
			}
		}
	}
	internal static class GrassCulling
	{
		private static readonly Plane[] Planes = (Plane[])(object)new Plane[6];

		private static Camera _camera;

		private static int _planesFrame = -1;

		internal static void Postfix(int3 renderChunk, ref bool __result)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if (!__result)
			{
				return;
			}
			Camera val = Camera();
			if (!((Object)(object)val == (Object)null))
			{
				int frameCount = Time.frameCount;
				if (_planesFrame != frameCount)
				{
					GeometryUtility.CalculateFrustumPlanes(val, Planes);
					_planesFrame = frameCount;
				}
				float cHUNK_SIZE = GrassChunking.CHUNK_SIZE;
				Vector3 val2 = default(Vector3);
				((Vector3)(ref val2))..ctor(((float)renderChunk.x + 0.5f) * cHUNK_SIZE, ((float)renderChunk.y + 0.5f) * cHUNK_SIZE, ((float)renderChunk.z + 0.5f) * cHUNK_SIZE);
				Bounds val3 = default(Bounds);
				((Bounds)(ref val3))..ctor(val2, new Vector3(cHUNK_SIZE, cHUNK_SIZE, cHUNK_SIZE));
				if (!GeometryUtility.TestPlanesAABB(Planes, val3))
				{
					__result = false;
				}
			}
		}

		private static Camera Camera()
		{
			if ((Object)(object)_camera != (Object)null)
			{
				return _camera;
			}
			MainCamera instance = MainCamera.instance;
			if ((Object)(object)instance != (Object)null)
			{
				_camera = ((Component)instance).GetComponent<Camera>();
			}
			if (!((Object)(object)_camera != (Object)null))
			{
				return _camera = Camera.main;
			}
			return _camera;
		}
	}
	public sealed class LateJoinFixFeature : ModFeature
	{
		private ModLog _log;

		private JoinArbiter _arbiter;

		private GameEvents _events;

		private ConfigEntry<float> _afterWarpDelay;

		private ConfigEntry<float> _warpForwardOffset;

		private ConfigEntry<float> _reviveHeightOffset;

		public override string Name => "LateJoinFix";

		public override string Description => "Warps players who join mid-run to the party member furthest from the summit, then revives them.";

		public override string Replaces => "Sapphire009-LateJoinFix";

		public override string Section => "Rejoin";

		internal void Configure(ConfigFile config, JoinArbiter arbiter, GameEvents events)
		{
			_arbiter = arbiter;
			_events = events;
			_afterWarpDelay = config.Bind<float>("Rejoin", "LateJoin_AfterWarpDelay", 2f, "Seconds between warping a late joiner and reviving them. Too short and the revive can land at their pre-warp position.");
			_warpForwardOffset = config.Bind<float>("Rejoin", "LateJoin_WarpForwardOffset", 2f, "How far (world forward) to offset the warp from the target player, so the two do not spawn inside each other.");
			_reviveHeightOffset = config.Bind<float>("Rejoin", "LateJoin_ReviveHeightOffset", 3f, "How far above the target player to revive the late joiner.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			_arbiter.LateJoinHandler = HandleLateJoin;
			_events.OnCharacterSpawned((Action<Character>)OnCharacterSpawned);
		}

		private IEnumerator HandleLateJoin(Player player, Character character)
		{
			Character target = PlayerUtil.FindFurthestFromSummit(character);
			if ((Object)(object)target == (Object)null)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Warning((object)"No living player to warp to; leaving the late joiner where they spawned.");
				}
				yield break;
			}
			Vector3 val = target.Center + Vector3.forward * _warpForwardOffset.Value;
			if (PlayerUtil.Warp(character, val))
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Info((object)("Warped '" + character.characterName + "' to '" + target.characterName + "' (lowest climber)."));
				}
			}
			else
			{
				ModLog log3 = _log;
				if (log3 != null)
				{
					log3.Warning((object)("Failed to warp '" + character.characterName + "'."));
				}
			}
			yield return (object)new WaitForSeconds(_afterWarpDelay.Value);
			Vector3 val2 = target.Center + new Vector3(0f, _reviveHeightOffset.Value, 0f);
			if (PlayerUtil.ReviveAt(character, val2))
			{
				ModLog log4 = _log;
				if (log4 != null)
				{
					log4.Info((object)("Revived '" + character.characterName + "'."));
				}
			}
			else
			{
				ModLog log5 = _log;
				if (log5 != null)
				{
					log5.Warning((object)("Failed to revive '" + character.characterName + "'."));
				}
			}
		}

		private void OnCharacterSpawned(Character character)
		{
			if (!NetRoleInfo.HasAuthority || (Object)(object)character == (Object)null || (Object)(object)character.data == (Object)null)
			{
				return;
			}
			PhotonView photonView = ((MonoBehaviourPun)character).photonView;
			Player val = ((photonView != null) ? photonView.Owner : null);
			if (val == null || _arbiter == null || (_arbiter.Classify(val) == JoinKind.None && !_arbiter.HasSeen(val)))
			{
				return;
			}
			bool flag = false;
			if (character.data.passedOut)
			{
				character.data.passedOut = false;
				flag = true;
			}
			if (character.data.deathTimer > 0f)
			{
				character.data.deathTimer = 0f;
				flag = true;
			}
			if (flag)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Info((object)("Cleared stuck downed state on '" + character.characterName + "'."));
				}
			}
		}

		internal void Detach()
		{
			if (_arbiter != null)
			{
				_arbiter.LateJoinHandler = null;
			}
		}
	}
	internal static class LightSampleCache
	{
		private static readonly Dictionary<long, Color> Cache = new Dictionary<long, Color>();

		private static float _gridSize = 1f;

		private static float _inverseGrid = 1f;

		private static int _capacity = 4096;

		private static ModLog _log;

		internal static long Hits;

		internal static long Misses;

		internal static void Configure(float gridSize, int capacity, ModLog log)
		{
			_gridSize = Mathf.Max(0.05f, gridSize);
			_inverseGrid = 1f / _gridSize;
			_capacity = Mathf.Max(256, capacity);
			_log = log;
		}

		internal static void Clear()
		{
			Cache.Clear();
			Hits = 0L;
			Misses = 0L;
		}

		internal static bool Prefix(Vector3 worldPos, ref Color __result, out long __state)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			__state = Key(worldPos);
			if (Cache.TryGetValue(__state, out var value))
			{
				__result = value;
				Hits++;
				return false;
			}
			Misses++;
			return true;
		}

		internal static void Postfix(Color __result, long __state)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (Cache.Count >= _capacity)
			{
				Cache.Clear();
				ModLog log = _log;
				if (log != null)
				{
					log.Debug((object)"Light sample cache full; cleared.");
				}
			}
			Cache[__state] = __result;
		}

		private static long Key(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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)
			long num = (long)Mathf.Floor(position.x * _inverseGrid) & 0x1FFFFF;
			long num2 = (long)Mathf.Floor(position.y * _inverseGrid) & 0x1FFFFF;
			long num3 = (long)Mathf.Floor(position.z * _inverseGrid) & 0x1FFFFF;
			return num | (num2 << 21) | (num3 << 42);
		}
	}
	public sealed class LightSampleCacheFeature : ModFeature
	{
		private ConfigEntry<float> _gridSize;

		private ConfigEntry<int> _capacity;

		public override string Name => "LightSampleCache";

		public override string Description => "Caches baked light volume samples, which cost a CPU 3D-texture readback per call.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Performance";

		public void Configure(ConfigFile config)
		{
			_gridSize = config.Bind<float>("Performance", "LightSampleGridSize", 1f, "World-space cell size for cached light samples, in metres. Smaller is more accurate and hits the cache less often.");
			_capacity = config.Bind<int>("Performance", "LightSampleCacheEntries", 4096, "Maximum cached samples. The cache clears wholesale when full, which is cheaper than tracking per-entry age for a value this cheap to recompute.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			LightSampleCache.Configure(_gridSize?.Value ?? 1f, _capacity?.Value ?? 4096, log);
			Type type = AccessTools.TypeByName("LightVolume");
			if (type == null)
			{
				((ModFeature)this).Fail("LightVolume type not found");
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "SamplePosition", new Type[2]
			{
				typeof(Vector3),
				typeof(bool)
			}, (Type[])null);
			if (methodInfo == null)
			{
				((ModFeature)this).Fail("LightVolume.SamplePosition not found");
				return;
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(LightSampleCache), "Prefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(LightSampleCache), "Postfix", (Type[])null, (Type[])null);
			if (!patches.PatchMethod((MethodBase)methodInfo, methodInfo2, methodInfo3, (MethodInfo)null, (MethodInfo)null))
			{
				((ModFeature)this).Fail("could not patch LightVolume.SamplePosition");
			}
			else
			{
				log.Info((object)$"Light sample cache active ({_gridSize?.Value ?? 1f}m grid).");
			}
		}

		public static void Clear()
		{
			LightSampleCache.Clear();
		}
	}
	public sealed class LobbyJoinFixFeature : ModFeature
	{
		public override string Name => "LobbyJoinFix";

		public override string Description => "Retired. PEAK now defers lobby invites until the session is connected, so this is no longer needed.";

		public override string Replaces => "IceMods-PeakLobbyJoinFix";

		public override bool DefaultEnabled => true;

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			if (log != null)
			{
				log.Info((object)"LobbyJoinFix is retired: PEAK's GameBooter already waits for ConnectedAndReady before consuming a pending invite. Nothing to patch.");
			}
		}
	}
	public sealed class NullGuardFeature : ModFeature
	{
		public override string Name => "NullGuards";

		public override string Description => "Stops two vanilla per-frame exceptions: pocket updates before a player exists, and particle culling with no observed character.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Fixes";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			int num = 0;
			MethodInfo methodInfo = AccessTools.Method(typeof(CharacterItems), "UpdatePocketBehaviors", (Type[])null, (Type[])null);
			if (methodInfo != null && patches.PatchMethod((MethodBase)methodInfo, AccessTools.Method(typeof(NullGuards), "PocketPrefix", (Type[])null, (Type[])null), (MethodInfo)null, (MethodInfo)null, (MethodInfo)null))
			{
				num++;
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(ParticleCountCuller), "Update", (Type[])null, (Type[])null);
			if (methodInfo2 != null && patches.PatchMethod((MethodBase)methodInfo2, AccessTools.Method(typeof(NullGuards), "ParticlePrefix", (Type[])null, (Type[])null), (MethodInfo)null, (MethodInfo)null, (MethodInfo)null))
			{
				num++;
			}
			if (num == 0)
			{
				((ModFeature)this).Fail("neither guard could be applied");
			}
			else
			{
				log.Info((object)$"Applied {num} of 2 null guards.");
			}
		}
	}
	internal static class NullGuards
	{
		internal static bool PocketPrefix(Character ___character)
		{
			if ((Object)(object)___character == (Object)null)
			{
				return false;
			}
			if ((Object)(object)___character.player == (Object)null || ___character.player.itemSlots == null)
			{
				return false;
			}
			return true;
		}

		internal static bool ParticlePrefix()
		{
			Character observedCharacter = Character.observedCharacter;
			if ((Object)(object)observedCharacter == (Object)null)
			{
				return false;
			}
			if (observedCharacter.refs != null)
			{
				return (Object)(object)observedCharacter.refs.head != (Object)null;
			}
			return false;
		}
	}
	public sealed class PerformanceFeature : ModFeature
	{
		internal static ModLog Log;

		internal static readonly Dictionary<CharacterRagdoll, float> LastUpdate = new Dictionary<CharacterRagdoll, float>();

		internal static ConfigEntry<float> NearRate;

		internal static ConfigEntry<float> MediumRate;

		internal static ConfigEntry<float> FarRate;

		internal static ConfigEntry<float> MediumDistance;

		internal static ConfigEntry<float> FarDistance;

		public override string Name => "PerformanceLOD";

		public override string Description => "Throttles physics updates for distant characters and avoids redundant rigidbody writes.";

		public override string Replaces => "KinTheInfinite-BetterFPS";

		public override string Section => "Performance";

		internal void Configure(ConfigFile config)
		{
			NearRate = config.Bind<float>("Performance", "NearUpdateRate", 60f, "Physics updates per second for nearby remote characters. 0 disables their updates entirely.");
			MediumRate = config.Bind<float>("Performance", "MediumUpdateRate", 30f, "Updates per second for characters past MediumDistance.");
			FarRate = config.Bind<float>("Performance", "FarUpdateRate", 10f, "Updates per second for characters past FarDistance.");
			MediumDistance = config.Bind<float>("Performance", "MediumDistance", 30f, "Distance (metres) at which a character drops to MediumUpdateRate.");
			FarDistance = config.Bind<float>("Performance", "FarDistance", 60f, "Distance (metres) at which a character drops to FarUpdateRate.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			Log = log;
			if (!patches.PatchType(typeof(RagdollThrottlePatch)))
			{
				((ModFeature)this).Fail("could not apply RagdollThrottlePatch");
			}
			if (!patches.PatchType(typeof(CenterOfMassCachePatch)))
			{
				((ModFeature)this).Fail("could not apply CenterOfMassCachePatch");
			}
		}

		internal static float DistanceFromLocalPlayer(Character character)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || (Object)(object)character == (Object)null)
			{
				return 0f;
			}
			Vector3 val = character.Center - localCharacter.Center;
			return ((Vector3)(ref val)).magnitude;
		}

		internal static void Reset()
		{
			LastUpdate.Clear();
		}
	}
	[HarmonyPatch(typeof(CharacterRagdoll), "FixedUpdate")]
	internal static class RagdollThrottlePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(CharacterRagdoll __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			Character field = ReflectionCache.GetField<Character>((object)__instance, "character", (Character)null);
			if ((Object)(object)field == (Object)null)
			{
				return true;
			}
			if (field.IsLocal || field.isScoutmaster)
			{
				return true;
			}
			float value = PerformanceFeature.NearRate.Value;
			float num = PerformanceFeature.DistanceFromLocalPlayer(field);
			if (num > PerformanceFeature.FarDistance.Value)
			{
				value = PerformanceFeature.FarRate.Value;
			}
			else if (num > PerformanceFeature.MediumDistance.Value)
			{
				value = PerformanceFeature.MediumRate.Value;
			}
			if (value <= 0f)
			{
				return false;
			}
			float num2 = 1f / value;
			float time = Time.time;
			if (!PerformanceFeature.LastUpdate.TryGetValue(__instance, out var value2))
			{
				PerformanceFeature.LastUpdate[__instance] = time;
				return true;
			}
			if (time - value2 < num2)
			{
				return false;
			}
			PerformanceFeature.LastUpdate[__instance] = time;
			return true;
		}
	}
	[HarmonyPatch(typeof(CenterOfMass), "FixedUpdate")]
	internal static class CenterOfMassCachePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(CenterOfMass __instance)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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)
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			Rigidbody field = ReflectionCache.GetField<Rigidbody>((object)__instance, "rb", (Rigidbody)null);
			if ((Object)(object)field == (Object)null)
			{
				return true;
			}
			if (__instance.onlyOnGround)
			{
				Item field2 = ReflectionCache.GetField<Item>((object)__instance, "item", (Item)null);
				if ((Object)(object)field2 != (Object)null && (int)field2.itemState != 0)
				{
					return false;
				}
			}
			Vector3 val = (((Object)(object)__instance.centerOfMassTransform != (Object)null) ? __instance.centerOfMassTransform.localPosition : __instance.localCenterOfMass);
			if (field.centerOfMass != val)
			{
				field.centerOfMass = val;
			}
			if (field.angularDamping != __instance.angularDamping)
			{
				field.angularDamping = __instance.angularDamping;
			}
			return false;
		}
	}
	public sealed class PhysicsThrottleFeature : ModFeature
	{
		private ConfigEntry<float> _shelterInterval;

		private ConfigEntry<bool> _optimizeBots;

		public override string Name => "PhysicsThrottle";

		public override string Description => "Caches wind shelter raycasts and removes the per-search scene scan from bot vision.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Performance";

		public void Configure(ConfigFile config)
		{
			_shelterInterval = config.Bind<float>("Performance", "WindShelterInterval", 0.1f, "Seconds between wind shelter raycasts per character. Terrain does not move, so this only needs to keep up with the player.");
			_optimizeBots = config.Bind<bool>("Performance", "OptimizeBotVision", true, "Replaces the scene scan in bot vision with the list the game already maintains.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			PhysicsThrottle.Configure(_shelterInterval?.Value ?? 0.1f, log);
			MethodInfo methodInfo = AccessTools.Method(typeof(WindChillZone), "AddWindForceToCharacter", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((ModFeature)this).Fail("WindChillZone.AddWindForceToCharacter not found");
				return;
			}
			if (!patches.PatchMethod((MethodBase)methodInfo, AccessTools.Method(typeof(PhysicsThrottle), "WindPrefix", (Type[])null, (Type[])null), (MethodInfo)null, (MethodInfo)null, AccessTools.Method(typeof(PhysicsThrottle), "WindFinalizer", (Type[])null, (Type[])null)))
			{
				((ModFeature)this).Fail("could not patch WindChillZone.AddWindForceToCharacter");
				return;
			}
			ConfigEntry<bool> optimizeBots = _optimizeBots;
			if (optimizeBots == null || optimizeBots.Value)
			{
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Bot), "LookForPlayerHead", (Type[])null, (Type[])null);
				if (methodInfo2 == null || !patches.PatchMethod((MethodBase)methodInfo2, AccessTools.Method(typeof(PhysicsThrottle), "BotPrefix", (Type[])null, (Type[])null), (MethodInfo)null, (MethodInfo)null, (MethodInfo)null))
				{
					log.Warning((object)"Bot vision not optimized; Bot.LookForPlayerHead could not be patched.");
				}
			}
			log.Info((object)"Physics throttling active.");
		}
	}
	internal static class PhysicsThrottle
	{
		private sealed class Shelter
		{
			public float Multiplier = 1f;

			public float NextQuery;
		}

		private static readonly ConditionalWeakTable<Character, Shelter> Shelters = new ConditionalWeakTable<Character, Shelter>();

		private static float _interval = 0.1f;

		private static ModLog _log;

		internal static void Configure(float interval, ModLog log)
		{
			_interval = Mathf.Max(0.02f, interval);
			_log = log;
		}

		internal static bool WindPrefix(WindChillZone __instance, Character character, out bool __state)
		{
			__state = false;
			if ((Object)(object)__instance == (Object)null || (Object)(object)character == (Object)null || !__instance.useRaycast)
			{
				return true;
			}
			Shelter orCreateValue = Shelters.GetOrCreateValue(character);
			if (Time.time >= orCreateValue.NextQuery)
			{
				orCreateValue.NextQuery = Time.time + _interval;
				return true;
			}
			__instance.useRaycast = false;
			__state = true;
			return true;
		}

		internal static void WindFinalizer(WindChillZone __instance, bool __state)
		{
			if (__state && (Object)(object)__instance != (Object)null)
			{
				__instance.useRaycast = true;
			}
		}

		internal static bool BotPrefix(Bot __instance, Vector3 searcherHeadPos, float maxRange, float maxAngle, ref Rigidbody __result)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: 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_00d4: Unknown result type (might be due to invalid IL or missing references)
			__result = null;
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters == null || allCharacters.Count == 0)
			{
				return false;
			}
			Character val = null;
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Character val2 = allCharacters[i];
				if (!((Object)(object)val2 == (Object)null) && !val2.isBot)
				{
					val = val2;
					break;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Vector3 center = __instance.Center;
			Vector3 center2 = val.Center;
			if (Vector3.Distance(center, center2) > maxRange)
			{
				return false;
			}
			if (Vector3.Angle(center2 - center, __instance.LookDirection) > maxAngle)
			{
				return false;
			}
			Bodypart val3 = val.refs?.head;
			if ((Object)(object)val3 == (Object)null || (Object)(object)val3.Rig == (Object)null)
			{
				return false;
			}
			RaycastHit val4 = HelperFunctions.LineCheck(searcherHeadPos, val3.Rig.position, (LayerType)1, 0f, (QueryTriggerInteraction)1);
			if (Object.op_Implicit((Object)(object)((RaycastHit)(ref val4)).transform))
			{
				return false;
			}
			__result = val3.Rig;
			return false;
		}
	}
	public sealed class PlatformTuningFeature : ModFeature
	{
		private ConfigEntry<bool> _reportPlatform;

		private ConfigEntry<int> _protonLoadingPriority;

		private ConfigEntry<int> _maxQueuedFrames;

		private ModLog _log;

		private const int EngineDefaultQueuedFrames = -1;

		public override string Name => "PlatformTuning";

		public override string Description => "Applies the few engine settings that genuinely differ under Proton/Wine versus native Windows. Does nothing on Windows unless explicitly configured.";

		public override string Replaces => "";

		public override string Section => "Performance";

		public void Configure(ConfigFile config)
		{
			_reportPlatform = config.Bind<bool>("Performance", "ReportPlatform", true, "Log which platform was detected and the evidence for it. Useful when reporting a performance problem, since Proton and Windows behave differently.");
			_protonLoadingPriority = config.Bind<int>("Performance", "ProtonBackgroundLoadingPriority", 2, "Proton/Wine only. How much time per frame the engine may spend on background loading, which is when DXVK compiles shader pipelines. 0 leaves the engine default, 1 is low, 2 is normal, 3 is high. Higher trades slightly longer loads for fewer first-encounter stutters. Ignored on native Windows.");
			_maxQueuedFrames = config.Bind<int>("Performance", "MaxQueuedFrames", -1, "Frames the CPU may queue ahead of the GPU. -1 leaves the engine default (2). Setting 1 can reduce input latency under DXVK, at some risk to throughput. Applies on any platform, because it is a preference rather than a fix.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			//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_0027: Unknown result type (might be due to invalid IL or missing references)
			_log = log;
			HostPlatform current = PlatformInfo.Current;
			ConfigEntry<bool> reportPlatform = _reportPlatform;
			if (reportPlatform == null || reportPlatform.Value)
			{
				log.Info((object)$"Platform: {current} ({PlatformInfo.Detail}).");
			}
			ApplyQueuedFrames(log);
			if (PlatformInfo.IsTranslated)
			{
				ApplyProtonTuning(log);
			}
			else
			{
				log.Debug((object)"Native platform: no translation-layer tuning applied.");
			}
		}

		private void ApplyProtonTuning(ModLog log)
		{
			//IL_002f: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			int num = _protonLoadingPriority?.Value ?? 2;
			if (num <= 0)
			{
				log.Debug((object)"Proton detected; background loading priority left at the engine default.");
				return;
			}
			try
			{
				ThreadPriority val = (Application.backgroundLoadingPriority = (ThreadPriority)(num switch
				{
					1 => 0, 
					2 => 2, 
					_ => 4, 
				}));
				log.Info((object)($"Proton detected: background loading priority set to {val}, so more " + "shader pipeline compilation happens during loads."));
			}
			catch (Exception ex)
			{
				log.Exception("Could not set background loading priority", ex);
				((ModFeature)this).Fail("background loading priority could not be set");
			}
		}

		private void ApplyQueuedFrames(ModLog log)
		{
			int num = _maxQueuedFrames?.Value ?? (-1);
			if (num < 0)
			{
				return;
			}
			try
			{
				QualitySettings.maxQueuedFrames = Mathf.Clamp(num, 1, 4);
				log.Info((object)$"Max queued frames set to {QualitySettings.maxQueuedFrames}.");
			}
			catch (Exception ex)
			{
				log.Exception("Could not set maxQueuedFrames", ex);
			}
		}
	}
	public sealed class ProximityGateFeature : ModFeature
	{
		private ConfigEntry<float> _trapDistance;

		public override string Name => "ProximityGating";

		public override string Description => "Skips per-step updates for traps and ambient particle spawners with no player nearby.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override string Section => "Performance";

		public void Configure(ConfigFile config)
		{
			_trapDistance = config.Bind<float>("Performance", "TrapActiveDistance", 80f, "Distance within which traps keep updating. Well beyond their own reach, so this cannot stop a trap that could actually hit someone.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			ProximityGate.Configure(_trapDistance?.Value ?? 80f, log);
			MethodInfo methodInfo = AccessTools.Method(typeof(ProximityGate), "TrapPrefix", (Type[])null, (Type[])null);
			int num = 0;
			Type[] array = new Type[2]
			{
				typeof(ArrowShooter),
				typeof(VenusFlyTrap)
			};
			foreach (Type type in array)
			{
				MethodInfo methodInfo2 = AccessTools.Method(type, "FixedUpdate", (Type[])null, (Type[])null);
				if (methodInfo2 == null)
				{
					log.Warning((object)(type.Name + ".FixedUpdate not found; it will not be gated."));
				}
				else if (patches.PatchMethod((MethodBase)methodInfo2, methodInfo, (MethodInfo)null, (MethodInfo)null, (MethodInfo)null))
				{
					num++;
				}
			}
			if (num == 0)
			{
				((ModFeature)this).Fail("no trap updates could be gated");
			}
			else
			{
				log.Info((object)$"Proximity gating active on {num} trap type(s).");
			}
		}
	}
	internal static class ProximityGate
	{
		private static float _distanceSqr = 6400f;

		private static ModLog _log;

		internal static void Configure(float distance, ModLog log)
		{
			_distanceSqr = distance * distance;
			_log = log;
		}

		internal static bool TrapPrefix(MonoBehaviour __instance)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			return IsAnyPlayerNear(((Component)__instance).transform.position);
		}

		internal static bool IsAnyPlayerNear(Vector3 position)
		{
			//IL_0028: 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_002e: 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_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			List<Character> allCharacters = Character.AllCharacters;
			Vector3 val;
			if (allCharacters == null || allCharacters.Count == 0)
			{
				MainCamera instance = MainCamera.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return true;
				}
				val = ((Component)instance).transform.position - position;
				return ((Vector3)(ref val)).sqrMagnitude <= _distanceSqr;
			}
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Character val2 = allCharacters[i];
				if (!((Object)(object)val2 == (Object)null) && !val2.IsGhost)
				{
					val = val2.Center - position;
					if (((Vector3)(ref val)).sqrMagnitude <= _distanceSqr)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	public sealed class ReconnectCatchupFeature : ModFeature
	{
		private ModLog _log;

		private JoinArbiter _arbiter;

		private ConfigEntry<float> _heightOffset;

		public override string Name => "ReconnectCatchup";

		public override string Description => "Warps returning players to the party member furthest from the summit instead of the level start.";

		public override string Replaces => "Ayzax-Reconnect_Catchup";

		public override string Section => "Rejoin";

		internal void Configure(ConfigFile config, JoinArbiter arbiter)
		{
			_arbiter = arbiter;
			_heightOffset = config.Bind<float>("Rejoin", "CatchUp_HeightOffset", 1f, "Metres above the target player's head to place a returning player.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			_arbiter.CatchUpHandler = HandleCatchUp;
		}

		private IEnumerator HandleCatchUp(Player player, Character character)
		{
			Character val = PlayerUtil.FindFurthestFromSummit(character);
			if ((Object)(object)val == (Object)null)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Warning((object)"No living player to catch up to; leaving the returning player where they spawned.");
				}
				yield break;
			}
			Vector3 val2 = val.Head + Vector3.up * _heightOffset.Value;
			if (PlayerUtil.Warp(character, val2))
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Info((object)("Caught '" + character.characterName + "' up to '" + val.characterName + "' (lowest climber)."));
				}
			}
			else
			{
				ModLog log3 = _log;
				if (log3 != null)
				{
					log3.Warning((object)("Failed to warp '" + character.characterName + "'."));
				}
			}
		}

		internal void Detach()
		{
			if (_arbiter != null)
			{
				_arbiter.CatchUpHandler = null;
			}
		}
	}
	public sealed class RejoinPositionFeature : ModFeature
	{
		private ModLog _log;

		private JoinArbiter _arbiter;

		private GameEvents _events;

		private ConfigEntry<float> _fogSafetyMargin;

		private ConfigEntry<float> _maxAwayTime;

		private Fog _fog;

		public override string Name => "RestoreRejoinPosition";

		public override string Description => "Returns a rejoining player to where they disconnected, if the gloom has not risen past it.";

		public override string Replaces => "(new behaviour)";

		public override string Section => "Rejoin";

		internal void Configure(ConfigFile config, JoinArbiter arbiter, GameEvents events)
		{
			_arbiter = arbiter;
			_events = events;
			_fogSafetyMargin = config.Bind<float>("Rejoin", "Restore_FogSafetyMargin", 10f, "Metres of clearance required between the gloom and a player's saved position before restoring them there.");
			_maxAwayTime = config.Bind<float>("Rejoin", "Restore_MaxAwayTimeSeconds", 300f, "Longest disconnect, in seconds, that still restores the original position. Past this they are caught up to the party instead.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			_arbiter.RestoreHandler = HandleRestore;
			_arbiter.CanRestorePosition = CanRestore;
			_events.OnPlayerDisconnected((Action<Player>)OnPlayerDisconnected);
		}

		internal float GetFogHeight()
		{
			if ((Object)(object)_fog == (Object)null)
			{
				_fog = Object.FindObjectOfType<Fog>();
			}
			if (!((Object)(object)_fog != (Object)null))
			{
				return float.MinValue;
			}
			return _fog.fogHeight;
		}

		private void OnPlayerDisconnected(Player player)
		{
			if (player != null && !PlayerUtil.InAirport)
			{
				Character character = PlayerUtil.GetCharacter(player);
				DisconnectedPlayerRegistry.Record(player, character, GetFogHeight(), _log);
			}
		}

		private bool CanRestore(Player player)
		{
			DisconnectedPlayerRegistry.DisconnectedState disconnectedState = DisconnectedPlayerRegistry.Find(player);
			if (disconnectedState == null)
			{
				return false;
			}
			float num = Time.realtimeSinceStartup - disconnectedState.DisconnectedAt;
			if (num > _maxAwayTime.Value)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Info((object)$"Not restoring '{disconnectedState.CharacterName}': away too long ({num:F0}s).");
				}
				return false;
			}
			if (disconnectedState.DiedWhileDisconnected)
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Info((object)("Not restoring '" + disconnectedState.CharacterName + "': they died while disconnected."));
				}
				return false;
			}
			float fogHeight = GetFogHeight();
			if (fogHeight > float.MinValue && fogHeight + _fogSafetyMargin.Value >= disconnectedState.LastPosition.y)
			{
				ModLog log3 = _log;
				if (log3 != null)
				{
					log3.Info((object)($"Not restoring '{disconnectedState.CharacterName}': the gloom has risen to {fogHeight:F1} " + $"and their spot was {disconnectedState.LastPosition.y:F1}."));
				}
				return false;
			}
			return true;
		}

		private IEnumerator HandleRestore(Player player, Character character)
		{
			DisconnectedPlayerRegistry.DisconnectedState disconnectedState = DisconnectedPlayerRegistry.Find(player);
			if (disconnectedState == null)
			{
				yield break;
			}
			if (PlayerUtil.Warp(character, disconnectedState.LastPosition))
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Info((object)$"Restored '{character.characterName}' to their disconnect position {disconnectedState.LastPosition}.");
				}
			}
			else
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Warning((object)("Failed to restore '" + character.characterName + "'."));
				}
			}
			DisconnectedPlayerRegistry.Clear(player);
		}

		internal void Detach()
		{
			if (_arbiter != null)
			{
				_arbiter.RestoreHandler = null;
				_arbiter.CanRestorePosition = null;
			}
		}
	}
	public sealed class RejoinVoiceFixFeature : ModFeature
	{
		internal static ModLog Log;

		public override string Name => "RejoinVoiceFix";

		public override string Description => "Resets Photon Voice when leaving a game so voice chat works in the next lobby.";

		public override string Replaces => "PatchNote-RejoinVoiceFix";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			Log = log;
			if (!patches.PatchType(typeof(LeaveCurrentGameVoiceResetPatch)))
			{
				((ModFeature)this).Fail("could not apply LeaveCurrentGameVoiceResetPatch");
			}
		}

		internal static void ResetVoice(string source)
		{
			PunVoiceClient instance = PunVoiceClient.Instance;
			LoadBalancingTransport val = ((instance != null) ? ((VoiceConnection)instance).Client : null);
			if (val == null)
			{
				return;
			}
			string text = ((((LoadBalancingClient)val).InRoom && ((LoadBalancingClient)val).CurrentRoom != null) ? (((LoadBalancingClient)val).CurrentRoom.Name ?? "<unknown>") : "<none>");
			try
			{
				((LoadBalancingClient)val).Disconnect();
				ModLog log = Log;
				if (log != null)
				{
					log.Info((object)("Voice disconnect requested. source=" + source + " room=" + text));
				}
			}
			catch (Exception ex)
			{
				ModLog log2 = Log;
				if (log2 != null)
				{
					log2.Exception("Voice disconnect threw while handling " + source, ex);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "LeaveCurrentGame")]
	internal static class LeaveCurrentGameVoiceResetPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			RejoinVoiceFixFeature.ResetVoice("Player.LeaveCurrentGame");
		}
	}
	public sealed class RopeShooterFixFeature : ModFeature
	{
		[HarmonyPatch(typeof(RopeShooter), "Update")]
		private static class UpdatePatch
		{
			private static readonly FieldInfo OverrideUsability = AccessTools.Field(typeof(Item), "overrideUsability");

			private static readonly object SomeTrue = MakeSome(value: true);

			private static readonly object SomeFalse = MakeSome(value: false);

			private static readonly object NoneValue = MakeNone();

			private static bool Ready
			{
				get
				{
					if (OverrideUsability != null && SomeTrue != null && SomeFalse != null)
					{
						return NoneValue != null;
					}
					return false;
				}
			}

			private static object MakeSome(bool value)
			{
				Type type = OverrideUsability?.FieldType;
				if (!(type == null))
				{
					return AccessTools.Method(type, "Some", (Type[])null, (Type[])null)?.Invoke(null, new object[1] { value });
				}
				return null;
			}

			private static object MakeNone()
			{
				Type type = OverrideUsability?.FieldType;
				object obj;
				if (!(type == null))
				{
					obj = AccessTools.Property(type, "None")?.GetValue(null);
					if (obj == null)
					{
						return AccessTools.Field(type, "None")?.GetValue(null);
					}
				}
				else
				{
					obj = null;
				}
				return obj;
			}

			private static bool Prefix(RopeShooter __instance)
			{
				if (!Ready)
				{
					return true;
				}
				Item item = ((ItemComponent)__instance).item;
				if ((Object)(object)item == (Object)null)
				{
					return false;
				}
				Character holderCharacter = item.holderCharacter;
				if ((Object)(object)holderCharacter == (Object)null || !holderCharacter.IsLocal)
				{
					OverrideUsability.SetValue(item, NoneValue);
					return false;
				}
				if (item.isUsingPrimary && !item.finishedCast)
				{
					OverrideUsability.SetValue(item, SomeTrue);
					return false;
				}
				RaycastHit val = default(RaycastHit);
				OverrideUsability.SetValue(item, __instance.WillAttach(ref val) ? SomeTrue : SomeFalse);
				return false;
			}
		}

		internal static ModLog Log;

		public override string Name => "RopeShooterFix";

		public override string Description => "Fixes the vanilla rope cannon refusing to fire: a single frame of lost footing or aim wobble cancelled the whole cast. Also stops spare cannons raycasting every frame.";

		public override string Replaces => "(vanilla bug)";

		public override string Section => "Fixes";

		public override bool DefaultEnabled => true;

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			Log = log;
			patches.PatchType(typeof(UpdatePatch));
		}
	}
	public sealed class SoftDependencyFixFeature : ModFeature
	{
		internal static ModLog Log;

		public override string Name => "SoftDependencyFix";

		public override string Description => "Keeps partially-loaded assemblies usable, so a mod with a missing optional dependency does not hide unrelated console commands.";

		public override string Replaces => "PEAKModding-SoftDependencyFix";

		public override bool DefaultEnabled => true;

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			Log = log;
			if (!patches.PatchType(typeof(ReflectionUtilityGetTypesPatch)))
			{
				((ModFeature)this).Fail("could not apply ReflectionUtilityGetTypesPatch");
			}
		}
	}
	internal static class ReflectionUtilityGetTypesPatch
	{
		[HarmonyTargetMethod]
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(ReflectionUtility), "GetMethodsWithAttribute", (Type[])null, new Type[1] { typeof(ConsoleCommandAttribute) });
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo original = AccessTools.Method(typeof(Assembly), "GetTypes", (Type[])null, (Type[])null);
			MethodInfo replacement = AccessTools.Method(typeof(ReflectionUtilityGetTypesPatch), "GetTypesSafe", (Type[])null, (Type[])null);
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (original != null && replacement != null && (instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Call) && instruction.operand is MethodInfo methodInfo && methodInfo == original)
				{
					replaced++;
					instruction.opcode = OpCodes.Call;
					instruction.operand = replacement;
					yield return instruction;
				}
				else
				{
					yield return instruction;
				}
			}
			if (replaced == 0)
			{
				ModLog log = SoftDependencyFixFeature.Log;
				if (log != null)
				{
					log.Warning((object)"SoftDependencyFix found no Assembly.GetTypes() call to patch; the game may have changed.");
				}
			}
			else
			{
				ModLog log2 = SoftDependencyFixFeature.Log;
				if (log2 != null)
				{
					log2.Info((object)$"SoftDependencyFix active: rewrote {replaced} Assembly.GetTypes() call(s) to tolerate missing optional dependencies.");
				}
			}
		}

		public static Type[] GetTypesSafe(Assembly assembly)
		{
			if (assembly == null)
			{
				return Array.Empty<Type>();
			}
			try
			{
				return assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				if (ex.Types == null)
				{
					return Array.Empty<Type>();
				}
				List<Type> list = new List<Type>(ex.Types.Length);
				Type[] types = ex.Types;
				foreach (Type type in types)
				{
					if (type != null)
					{
						list.Add(type);
					}
				}
				ModLog log = SoftDependencyFixFeature.Log;
				if (log != null)
				{
					log.Info((object)("'" + assembly.GetName().Name + "' has an unresolved optional dependency; " + $"recovered {list.Count} of {ex.Types.Length} types instead of discarding the assembly."));
				}
				return list.ToArray();
			}
			catch (Exception ex2)
			{
				ModLog log2 = SoftDependencyFixFeature.Log;
				if (log2 != null)
				{
					log2.Debug((object)("Could not read types from '" + assembly.GetName().Name + "': " + ex2.GetType().Name + "."));
				}
				return Array.Empty<Type>();
			}
		}
	}
	public sealed class TelemetryFeature : ModFeature
	{
		private ConfigEntry<float> _interval;

		private ConfigEntry<KeyCode> _hotkey;

		private string _text = "";

		private float _nextRefresh;

		private bool _visible = true;

		private float _smoothedMs;

		public override string Name => "Telemetry";

		public override string Description => "Shows frame time and what each optimization is doing, so the effect can be measured.";

		public override string Replaces => "reis637-PeakOptimizer";

		public override bool DefaultEnabled => false;

		public override string Section => "Diagnostics";

		public LinkQuality Quality { get; set; }

		public void Configure(ConfigFile config)
		{
			_interval = config.Bind<float>("Diagnostics", "TelemetryInterval", 0.5f, "Seconds between readout refreshes.");
			_hotkey = config.Bind<KeyCode>("Diagnostics", "TelemetryToggleKey", (KeyCode)289, "Key that shows or hides the readout while the feature is enabled.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyCode> hotkey = _hotkey;
			log.Info((object)$"Telemetry enabled; press {(object)(KeyCode)((hotkey == null) ? 289 : ((int)hotkey.Value))} to toggle it.");
		}

		public void Tick()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyCode> hotkey = _hotkey;
			if (Input.GetKeyDown((KeyCode)((hotkey == null) ? 289 : ((int)hotkey.Value))))
			{
				_visible = !_visible;
			}
			float num = Time.unscaledDeltaTime * 1000f;
			_smoothedMs = ((_smoothedMs <= 0f) ? num : Mathf.Lerp(_smoothedMs, num, 0.1f));
			if (!(Time.unscaledTime < _nextRefresh))
			{
				_nextRefresh = Time.unscaledTime + Mathf.Max(0.1f, _interval?.Value ?? 0.5f);
				Rebuild();
			}
		}

		private void AppendNetwork(StringBuilder sb)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!PhotonNetwork.InRoom)
				{
					return;
				}
				LinkQuality quality = Quality;
				if (quality != null && quality.SmoothedRtt > 0)
				{
					sb.Append("link  ").Append(quality.Tier).Append("  ")
						.Append(quality.SmoothedRtt)
						.Append("ms  jitter ")
						.Append(quality.SmoothedJitter)
						.Append("ms");
					if (quality.RecentResends > 0)
					{
						sb.Append("  resends ").Append(quality.RecentResends);
					}
					sb.AppendLine();
					if (quality.UploadBytesPerSecond > 0 || quality.DownloadBytesPerSecond > 0)
					{
						sb.Append("traffic  up ").Append(((float)quality.UploadBytesPerSecond / 1024f).ToString("0.0")).Append(" KiB/s  down ")
							.Append(((float)quality.DownloadBytesPerSecond / 1024f).ToString("0.0"))
							.AppendLine(" KiB/s");
					}
				}
				sb.Append("send rate  ").Append(PhotonNetwork.SerializationRate).AppendLine("Hz");
				List<KeyValuePair<string, LinkTier>> list = (from pair in LinkReporter.All()
					where (int)pair.Value > 0
					select pair).ToList();
				if (list.Count <= 1)
				{
					return;
				}
				foreach (KeyValuePair<string, LinkTier> item in list)
				{
					sb.Append("  ").Append(item.Key).Append("  ")
						.AppendLine(((object)item.Value/*cast due to .constrained prefix*/).ToString());
				}
			}
			catch (Exception)
			{
			}
		}

		private void Rebuild()
		{
			StringBuilder stringBuilder = new StringBuilder(256);
			stringBuilder.Append("SpongePEAK  ").Append(_smoothedMs.ToString("0.0")).Append(" ms  (")
				.Append((1000f / Mathf.Max(0.01f, _smoothedMs)).ToString("0"))
				.AppendLine(" fps)");
			long hits = LightSampleCache.Hits;
			long misses = LightSampleCache.Misses;
			long num = hits + misses;
			if (num > 0)
			{
				stringBuilder.Append("light cache  ").Append((100f * (float)hits / (float)num).ToString("0.0")).Append("% of ")
					.Append(num)
					.AppendLine(" samples");
			}
			if (DecorationRegistry.TrackedCount > 0)
			{
				stringBuilder.Append("decorations  ").Append(DecorationRegistry.CulledCount).Append(" hidden of ")
					.AppendLine(DecorationRegistry.TrackedCount.ToString());
			}
			AppendNetwork(stringBuilder);
			_text = stringBuilder.ToString();
		}

		public void Draw()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			if (_visible && !string.IsNullOrEmpty(_text))
			{
				Rect val = default(Rect);
				((Rect)(ref val))..ctor(12f, 12f, 420f, 90f);
				Color color = GUI.color;
				GUI.color = Color.black;
				GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), _text);
				GUI.color = Color.white;
				GUI.Label(val, _text);
				GUI.color = color;
			}
		}
	}
	public sealed class VineClimbFixFeature : ModFeature
	{
		public override string Name => "VineClimbFix";

		public override string Description => "Stops rubber-banding when climbing vines and chains.";

		public override string Replaces => "Cryo-PEAK_VineFix";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			if (!patches.PatchType(typeof(VineClimbSyncPatch)))
			{
				((ModFeature)this).Fail("could not apply VineClimbSyncPatch");
			}
		}
	}
	[HarmonyPatch(typeof(CharacterVineClimbing), "RPCA_SyncVineClimb")]
	internal static class VineClimbSyncPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(CharacterVineClimbing __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			Character field = ReflectionCache.GetField<Character>((object)__instance, "character", (Character)null);
			if