Decompiled source of SpongePEAKQOL v1.0.0

plugins/com.sponge.peakqol.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using SpongePEAK.Core.Game;
using SpongePEAK.Core.Logging;
using SpongePEAK.Core.Patching;
using SpongePEAK.Core.Reflection;
using SpongePEAK.Lib.Events;
using SpongePEAK.Lib.Features;
using SpongePEAK.Lib.Items;
using SpongePEAK.Lib.Net;
using SpongePEAK.Lib.Plugin;
using SpongePEAK.Lib.UI;
using SpongePEAK.QOL.Features;
using TMPro;
using UnityEngine;

[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("Quality-of-life improvements, each individually toggleable.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+ddcbf023fcb4c2065ad9b038910633cd4a3a93b0")]
[assembly: AssemblyProduct("SpongePEAKQOL")]
[assembly: AssemblyTitle("com.sponge.peakqol")]
[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.QOL
{
	[BepInPlugin("com.sponge.peakqol", "SpongePEAKQOL", "1.0.0")]
	public sealed class QolPlugin : SpongePlugin
	{
		public const string Guid = "com.sponge.peakqol";

		private const string RulesKey = "sponge.qolrules";

		private FeatureRegistry _registry;

		private GameEvents _events;

		private HostRuleSet _rules;

		private PushFeature _push;

		private RopeCutFeature _ropeCut;

		private ItemInfoFeature _itemInfo;

		private MushroomInfoFeature _mushroomInfo;

		private TeammateStatsFeature _teammateStats;

		private CatchPlayersFeature _catchPlayers;

		private BackpacksForEveryoneFeature _backpacks;

		protected override string DisplayName => "SpongePEAKQOL";

		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_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			_registry = new FeatureRegistry(((BaseUnityPlugin)this).Config, ((SpongePlugin)this).Log);
			_events = new GameEvents(((SpongePlugin)this).Log);
			_rules = new HostRuleSet("sponge.qolrules", ((SpongePlugin)this).Log);
			_rules.Declare("AllowRopeSwinging", true);
			_rules.Declare("AllowPushing", true);
			_rules.Declare("AllowRopeCutting", true);
			RopeSwingFeature.Rules = _rules;
			PushFeature.Rules = _rules;
			RopeCutFeature.Rules = _rules;
			LastChanceFeature lastChanceFeature = new LastChanceFeature();
			lastChanceFeature.Configure(((BaseUnityPlugin)this).Config);
			_itemInfo = new ItemInfoFeature();
			_itemInfo.Configure(((BaseUnityPlugin)this).Config);
			_mushroomInfo = new MushroomInfoFeature();
			_mushroomInfo.Configure(((BaseUnityPlugin)this).Config);
			_teammateStats = new TeammateStatsFeature();
			_teammateStats.Configure(((BaseUnityPlugin)this).Config);
			RopeSwingFeature ropeSwingFeature = new RopeSwingFeature();
			ropeSwingFeature.Configure(((BaseUnityPlugin)this).Config);
			_push = new PushFeature();
			_push.Configure(((BaseUnityPlugin)this).Config);
			_ropeCut = new RopeCutFeature();
			_ropeCut.Configure(((BaseUnityPlugin)this).Config);
			_catchPlayers = new CatchPlayersFeature();
			_catchPlayers.Configure(((BaseUnityPlugin)this).Config);
			_backpacks = new BackpacksForEveryoneFeature();
			_backpacks.Configure(((BaseUnityPlugin)this).Config, (MonoBehaviour)(object)this);
			_registry.Register((ModFeature)(object)new DroppablePassportFeature());
			_registry.Register((ModFeature)(object)lastChanceFeature);
			_registry.Register((ModFeature)(object)new GhostPingFeature());
			_registry.Register((ModFeature)(object)_itemInfo);
			_registry.Register((ModFeature)(object)_mushroomInfo);
			_registry.Register((ModFeature)(object)_teammateStats);
			_registry.Register((ModFeature)(object)_catchPlayers);
			_registry.Register((ModFeature)(object)_backpacks);
			_registry.Register((ModFeature)(object)new CustomItemsFeature());
			_registry.Register((ModFeature)(object)ropeSwingFeature);
			_registry.Register((ModFeature)(object)_push);
			_registry.Register((ModFeature)(object)_ropeCut);
			_registry.EnableAll(((SpongePlugin)this).Patches);
			_rules.Start();
			if (((ModFeature)_backpacks).IsActive)
			{
				_events.OnCharacterSpawned((Action<Character>)delegate
				{
					_backpacks.OnRunStarted();
				});
				_events.OnRunEnded((Action)delegate
				{
					_backpacks.OnRunEnded();
				});
			}
		}

		private void Update()
		{
			if (_itemInfo != null && ((ModFeature)_itemInfo).IsActive)
			{
				_itemInfo.Tick();
			}
			if (_mushroomInfo != null && ((ModFeature)_mushroomInfo).IsActive)
			{
				_mushroomInfo.Tick();
			}
			if (_teammateStats != null && ((ModFeature)_teammateStats).IsActive)
			{
				_teammateStats.Tick();
			}
			if (_catchPlayers != null && ((ModFeature)_catchPlayers).IsActive)
			{
				_catchPlayers.Tick();
			}
		}

		protected override void OnUnload()
		{
			GameEvents events = _events;
			if (events != null)
			{
				events.UnsubscribeAll();
			}
			_itemInfo?.Cleanup();
			_mushroomInfo?.Cleanup();
			_teammateStats?.Cleanup();
			HostRuleSet rules = _rules;
			if (rules != null)
			{
				rules.Stop();
			}
			RopeSwingFeature.Rules = null;
			PushFeature.Rules = null;
			RopeCutFeature.Rules = null;
		}
	}
}
namespace SpongePEAK.QOL.Features
{
	public sealed class BackpacksForEveryoneFeature : ModFeature
	{
		private const string ItemResourcePath = "0_Items/";

		internal static ConfigEntry<string> BackpackPrefab;

		internal static ConfigEntry<float> SpawnDelay;

		private ModLog _log;

		private MonoBehaviour _coroutineHost;

		private bool _givenThisRun;

		public override string Name => "BackpacksForEveryone";

		public override string Description => "Gives every player a backpack when a run starts, if they do not already have one. Host only.";

		public override string Replaces => "Nozz-BagsForEveryone";

		public override string Section => "Items";

		internal void Configure(ConfigFile config, MonoBehaviour coroutineHost)
		{
			_coroutineHost = coroutineHost;
			BackpackPrefab = config.Bind<string>("Items", "Backpacks_PrefabName", "Backpack", "Name of the backpack prefab to spawn, resolved under the game's 0_Items resource path.");
			SpawnDelay = config.Bind<float>("Items", "Backpacks_SpawnDelaySeconds", 3f, "Seconds to wait after a run starts before handing out backpacks, so inventories are ready.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			if (!NetRoleInfo.HasAuthority)
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Info((object)"BackpacksForEveryone is host-driven: this client is not authoritative, so the host decides.");
				}
			}
		}

		internal void OnRunStarted()
		{
			if (!_givenThisRun && NetRoleInfo.HasAuthority)
			{
				_givenThisRun = true;
				if ((Object)(object)_coroutineHost != (Object)null)
				{
					_coroutineHost.StartCoroutine(GiveBackpacks());
				}
			}
		}

		internal void OnRunEnded()
		{
			_givenThisRun = false;
		}

		private IEnumerator GiveBackpacks()
		{
			yield return (object)new WaitForSeconds(SpawnDelay?.Value ?? 3f);
			if (!PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
			{
				yield break;
			}
			int num = 0;
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters == null)
			{
				yield break;
			}
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Character val = allCharacters[i];
				if (!((Object)(object)val == (Object)null) && !val.isBot)
				{
					Player player = val.player;
					if (!((Object)(object)player == (Object)null) && player.backpackSlot != null && ((ItemSlot)player.backpackSlot).IsEmpty() && TrySpawnBackpackFor(val))
					{
						num++;
					}
				}
			}
			if (num > 0)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Info((object)$"Handed out {num} backpack(s) at run start.");
				}
			}
		}

		private bool TrySpawnBackpackFor(Character character)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: 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)
			//IL_003f: 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)
			try
			{
				Vector3 val = character.Center + Vector3.up * 0.5f;
				GameObject val2 = PhotonNetwork.Instantiate("0_Items/" + (BackpackPrefab?.Value ?? "Backpack"), val, Quaternion.identity, (byte)0, (object[])null);
				if ((Object)(object)val2 == (Object)null)
				{
					return false;
				}
				Item val3 = default(Item);
				if (!val2.TryGetComponent<Item>(ref val3))
				{
					return false;
				}
				PhotonView component = ((Component)character).GetComponent<PhotonView>();
				if ((Object)(object)component != (Object)null)
				{
					val3.RequestPickup(component);
				}
				return true;
			}
			catch (Exception ex)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Exception("Could not give a backpack to '" + character.characterName + "'", ex);
				}
				return false;
			}
		}
	}
	public sealed class CatchPlayersFeature : ModFeature
	{
		internal static ConfigEntry<float> CatchRadius;

		internal static ConfigEntry<float> MinFallTime;

		private ModLog _log;

		public override string Name => "CatchFallingPlayers";

		public override string Description => "Cushions the landing of teammates who fall close to you, using the game's own catch mechanic.";

		public override string Replaces => "tony4twenty-Catch_Players";

		public override string Section => "Survival";

		internal void Configure(ConfigFile config)
		{
			CatchRadius = config.Bind<float>("Survival", "Catch_Radius", 4f, "Metres within which a falling teammate's landing is cushioned.");
			MinFallTime = config.Bind<float>("Survival", "Catch_MinFallSeconds", 0.6f, "How long someone must have been airborne before catching applies, so ordinary jumps are ignored.");
		}

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

		private static void LimitFalling(Character character)
		{
			MethodInfo methodInfo = ReflectionCache.Method(typeof(Character), "LimitFalling");
			if (methodInfo == null)
			{
				return;
			}
			try
			{
				methodInfo.Invoke(character, null);
			}
			catch
			{
			}
		}

		internal void Tick()
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead || localCharacter.data.passedOut)
			{
				return;
			}
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters == null)
			{
				return;
			}
			float num = CatchRadius?.Value ?? 4f;
			float num2 = num * num;
			float num3 = MinFallTime?.Value ?? 0.6f;
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Character val = allCharacters[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)localCharacter) && !((Object)(object)val.data == (Object)null) && !val.isBot && !val.data.dead && !(val.data.sinceGrounded < num3))
				{
					Vector3 val2 = val.Center - localCharacter.Center;
					if (!(((Vector3)(ref val2)).sqrMagnitude > num2))
					{
						LimitFalling(val);
					}
				}
			}
		}
	}
	public sealed class CustomItemsFeature : ModFeature
	{
		public override string Name => "ExampleCustomItems";

		public override string Description => "Adds example cloned items (Grape Berrynana, Nuclear Dynamite) that spawn naturally. Demonstrates the item cloning API.";

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

		public override string Section => "Items";

		public override bool DefaultEnabled => true;

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			ItemCloner.SetLogger(log);
			RegisterGrapeBanana();
			RegisterNuclearDynamite();
			RegisterBananaTorch();
			RegisterImposterBanana();
			if (log != null)
			{
				log.Info((object)"Registered example cloned items; they build once the item database loads.");
			}
		}

		private static void RegisterGrapeBanana()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			ItemCloner.Register(new ItemCloneDefinition("Yellow Berrynana", "SpongeGrapeBanana", "Grape Berrynana").WithAlternateSources(new string[4] { "Berrynana Yellow", "Pink Berrynana", "Blue Berrynana", "Brown Berrynana" }).WithDescription("A berrynana that got ideas.").WithHueShift(270f, 1.15f, 1f)
				.WithSpawning(new SpawnRule().InPool((SpawnPool)16, (Rarity)1).InPool((SpawnPool)4096, (Rarity)2).InPool((SpawnPool)536870912, (Rarity)5))
				.WithEffectsWhenCooked(0, (StatusEffect[])(object)new StatusEffect[2]
				{
					StatusEffect.FromPoints((STATUSTYPE)6, 40f),
					StatusEffect.FromPoints((STATUSTYPE)1, -10f)
				})
				.WithEffectsWhenCooked(1, (StatusEffect[])(object)new StatusEffect[2]
				{
					StatusEffect.FromPoints((STATUSTYPE)6, 20f),
					StatusEffect.FromPoints((STATUSTYPE)1, -30f)
				})
				.WithEffectsWhenCooked(2, (StatusEffect[])(object)new StatusEffect[1] { StatusEffect.FromPoints((STATUSTYPE)1, -50f) })
				.WithEffectsWhenCooked(4, (StatusEffect[])(object)new StatusEffect[2]
				{
					StatusEffect.FromPoints((STATUSTYPE)1, -25f),
					StatusEffect.FromPoints((STATUSTYPE)3, 20f)
				})
				.WithEffectsWhenCooked(5, (StatusEffect[])(object)new StatusEffect[1] { StatusEffect.FromPoints((STATUSTYPE)3, 40f) }));
		}

		private static void RegisterNuclearDynamite()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: 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_00a0: Expected O, but got Unknown
			//IL_00a5: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			ItemCloner.Register(new ItemCloneDefinition("Dynamite", "SpongeNuclearDynamite", "Nuclear Dynamite").WithDescription("Light the fuse. Then run, and keep running.").WithFuse(30f, true).WithHueShift(120f, 1.3f, 1f)
				.WithScale(5f)
				.WithExplosion(new ExplosionOverride
				{
					RadiusMultiplier = 200f,
					DamageMultiplier = 200f,
					ItemForceMultiplier = 200f,
					KnockbackMultiplier = 200f,
					Cloud = new StatusCloud
					{
						Status = (STATUSTYPE)3,
						Amount = 0.5f,
						RadiusFraction = 0.5f
					}
				})
				.WithSpawning(new SpawnRule
				{
					BanInSolo = true
				}.InPool((SpawnPool)8192, (Rarity)5).InPoolWithWeight((SpawnPool)536870912, 1)));
		}

		private static void RegisterBananaTorch()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			ItemCloner.Register(new ItemCloneDefinition("Yellow Berrynana", "SpongeBananaBaton", "Berrynana Baton").WithAlternateSources(new string[2] { "Berrynana Yellow", "Pink Berrynana" }).WithHueShift(30f, 0.7f, 1.2f).WithVariant((SpecialVariant)1)
				.WithScale(1.4f)
				.WithBehaviour(new ItemBehaviourSpec().ClearActions().Remove<ItemCooking>().Add<Action_ApplyInfiniteStamina>((Action<Action_ApplyInfiniteStamina>)delegate(Action_ApplyInfiniteStamina action)
				{
					action.buffTime = 6f;
					action.drowsyAmount = 0f;
					((ItemAction)action).OnCastFinished = true;
				})
					.ConfigureUIData((Action<Item>)delegate(Item item)
					{
						if (item.UIData != null)
						{
							item.UIData.mainInteractPrompt = "Use";
							item.UIData.canDrop = true;
							item.UIData.canThrow = true;
						}
					})));
		}

		private static void RegisterImposterBanana()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			ItemCloner.Register(new ItemCloneDefinition("Yellow Berrynana", "SpongeImposterBanana", "Yellow Berrynana").WithAlternateSources(new string[2] { "Berrynana Yellow", "Pink Berrynana" }).WithExplodeOnConsume("Dynamite"));
		}
	}
	public sealed class DroppablePassportFeature : ModFeature
	{
		private const string PassportItemName = "Passport";

		public override string Name => "DroppablePassport";

		public override string Description => "Lets you drop and throw the Passport instead of it occupying a slot permanently.";

		public override string Replaces => "Icebox-IForgorPassport";

		public override string Section => "Items";

		internal static string TargetItem => "Passport";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			if (!patches.PatchType(typeof(PassportDropPatch)))
			{
				((ModFeature)this).Fail("could not apply PassportDropPatch");
			}
		}
	}
	[HarmonyPatch(typeof(Item), "Awake")]
	internal static class PassportDropPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Item __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && __instance.UIData != null && !(__instance.UIData.itemName != DroppablePassportFeature.TargetItem))
			{
				__instance.UIData.canDrop = true;
				__instance.UIData.canThrow = true;
			}
		}
	}
	public sealed class GhostPingFeature : ModFeature
	{
		public override string Name => "GhostPing";

		public override string Description => "Lets dead players ping locations, so they can still help the group navigate.";

		public override string Replaces => "boxofbiscuits97-GhostPing";

		public override string Section => "Communication";

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			if (!patches.PatchType(typeof(GhostPingPatch)))
			{
				((ModFeature)this).Fail("could not apply GhostPingPatch");
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class GhostPingPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PointPinger __instance, ref bool __result)
		{
			if (!__result)
			{
				Character value = Traverse.Create((object)__instance).Field("character").GetValue<Character>();
				if (!((Object)(object)value == (Object)null) && !((Object)(object)value.data == (Object)null) && (value.IsGhost || value.data.dead))
				{
					bool value2 = Traverse.Create((object)__instance).Property("inCooldown", (object[])null).GetValue<bool>();
					__result = !value2;
				}
			}
		}
	}
	public sealed class ItemInfoFeature : ModFeature
	{
		internal static ConfigEntry<bool> ShowUses;

		internal static ConfigEntry<float> FontSize;

		private TextMeshProUGUI _label;

		private ModLog _log;

		private string _lastText = string.Empty;

		private Item _lastItem;

		private int _lastUses = int.MinValue;

		public override string Name => "ItemInfo";

		public override string Description => "Shows remaining uses and details for the item you are holding.";

		public override string Replaces => "Yueby-ItemInfo";

		public override string Section => "Display";

		internal void Configure(ConfigFile config)
		{
			ShowUses = config.Bind<bool>("Display", "ItemInfo_ShowUses", true, "Shows remaining uses for consumable items.");
			FontSize = config.Bind<float>("Display", "ItemInfo_FontSize", 18f, "Font size for the held-item readout.");
		}

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

		internal void Tick()
		{
			Character localCharacter = Character.localCharacter;
			Item val = (((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.data != (Object)null) ? localCharacter.data.currentItem : null);
			if ((Object)(object)val == (Object)null)
			{
				_lastItem = null;
				_lastUses = int.MinValue;
				SetText(string.Empty);
				return;
			}
			int remainingUses = GetRemainingUses(val);
			if (val != _lastItem || remainingUses != _lastUses)
			{
				_lastItem = val;
				_lastUses = remainingUses;
				SetText(BuildReadout(val, remainingUses));
			}
		}

		private static string BuildReadout(Item item, int remaining)
		{
			string text = ((item.UIData != null) ? item.UIData.itemName : null);
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			if (ShowUses == null || !ShowUses.Value)
			{
				return text;
			}
			if (item.totalUses <= 0)
			{
				return text;
			}
			if (remaining < 0)
			{
				return text;
			}
			return $"{text}  <color=#B0B0B0>{remaining}/{item.totalUses}</color>";
		}

		private static int GetRemainingUses(Item item)
		{
			try
			{
				if (!item.HasData((DataEntryKey)2))
				{
					return -1;
				}
				OptionableIntItemData data = item.GetData<OptionableIntItemData>((DataEntryKey)2);
				if (data == null || !data.HasData)
				{
					return -1;
				}
				return data.Value;
			}
			catch
			{
				return -1;
			}
		}

		private void SetText(string text)
		{
			if (text == _lastText)
			{
				return;
			}
			if (string.IsNullOrEmpty(text))
			{
				if ((Object)(object)_label != (Object)null)
				{
					((TMP_Text)_label).text = string.Empty;
				}
				_lastText = text;
				return;
			}
			if ((Object)(object)_label == (Object)null)
			{
				_label = HudRoot.CreateLabel("SpongeQOL_ItemInfo", (UiAnchor)3, FontSize?.Value ?? 18f);
				if ((Object)(object)_label == (Object)null)
				{
					return;
				}
				ModLog log = _log;
				if (log != null)
				{
					log.Debug((object)"ItemInfo label created.");
				}
			}
			((TMP_Text)_label).text = text;
			_lastText = text;
		}

		internal void Cleanup()
		{
			if ((Object)(object)_label != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_label).gameObject);
			}
			_label = null;
			_lastText = string.Empty;
		}
	}
	public sealed class LastChanceFeature : ModFeature
	{
		private const float PassOutThreshold = 0.999f;

		internal static ConfigEntry<bool> Enabled;

		public override string Name => "LastChanceCast";

		public override string Description => "Delays passing out while you are mid-cast, so a last-second item use is not wasted.";

		public override string Replaces => "IntRabbit-Last_Chance";

		public override string Section => "Survival";

		internal static float Threshold => 0.999f;

		internal void Configure(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Survival", "LastChance_Enabled", true, "Holds off passing out while an item cast is in progress. The reprieve ends as soon as the cast finishes or is cancelled.");
		}

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

		internal static bool IsMidCast(Character character)
		{
			if ((Object)(object)character == (Object)null || (Object)(object)character.data == (Object)null)
			{
				return false;
			}
			Item currentItem = character.data.currentItem;
			if ((Object)(object)currentItem == (Object)null)
			{
				return false;
			}
			if (currentItem.castProgress > 0f)
			{
				return !currentItem.finishedCast;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "HandleLife")]
	internal static class LastChancePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Character __instance)
		{
			if (LastChanceFeature.Enabled == null || !LastChanceFeature.Enabled.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.data == (Object)null || __instance.refs == null)
			{
				return true;
			}
			CharacterAfflictions afflictions = __instance.refs.afflictions;
			if ((Object)(object)afflictions == (Object)null)
			{
				return true;
			}
			if (afflictions.statusSum < 1f)
			{
				return true;
			}
			if (__instance.data.passOutValue <= LastChanceFeature.Threshold)
			{
				return true;
			}
			return !LastChanceFeature.IsMidCast(__instance);
		}
	}
	public sealed class MushroomInfoFeature : ModFeature
	{
		private const int EffectCount = 10;

		internal static ConfigEntry<KeyCode> ToggleKey;

		internal static ConfigEntry<bool> StartVisible;

		internal static ConfigEntry<float> FontSize;

		private TextMeshProUGUI _label;

		private ModLog _log;

		private bool _visible;

		private MushroomManager _lastManager;

		private string _cached;

		public override string Name => "MushroomInfo";

		public override string Description => "Shows what each mushroom does in this run, instead of learning by trial and error.";

		public override string Replaces => "Kirshoo-MushroomsNOW";

		public override string Section => "Display";

		internal void Configure(ConfigFile config)
		{
			ToggleKey = config.Bind<KeyCode>("Display", "MushroomInfo_ToggleKey", (KeyCode)288, "Key that shows or hides the mushroom effect list.");
			StartVisible = config.Bind<bool>("Display", "MushroomInfo_StartVisible", false, "Whether the list is shown without pressing the toggle key.");
			FontSize = config.Bind<float>("Display", "MushroomInfo_FontSize", 15f, "Font size for the mushroom effect list.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			_visible = StartVisible?.Value ?? false;
		}

		internal void Tick()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if (!GameState.InRun)
			{
				SetText(string.Empty);
				return;
			}
			if (ToggleKey != null && Input.GetKeyDown(ToggleKey.Value))
			{
				_visible = !_visible;
			}
			MushroomManager instance = MushroomManager.instance;
			if (!_visible || (Object)(object)instance == (Object)null)
			{
				SetText(string.Empty);
				return;
			}
			if ((Object)(object)instance != (Object)(object)_lastManager || _cached == null)
			{
				_lastManager = instance;
				_cached = BuildReadout(instance);
			}
			SetText(_cached);
		}

		private static string BuildReadout(MushroomManager manager)
		{
			int[] mushroomEffects = manager.mushroomEffects;
			if (mushroomEffects == null || mushroomEffects.Length == 0)
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<b>Mushrooms</b>\n");
			int num = Mathf.Min(mushroomEffects.Length, 10);
			for (int i = 0; i < num; i++)
			{
				int effect = mushroomEffects[i];
				string arg = (IsGood(effect) ? "#7FD97F" : "#E08A8A");
				stringBuilder.Append($"<color={arg}>#{i + 1} {DescribeEffect(effect)}</color>\n");
			}
			return stringBuilder.ToString();
		}

		private static bool IsGood(int effect)
		{
			int[] goodEffects = Action_RandomMushroomEffect.GoodEffects;
			if (goodEffects == null)
			{
				return effect <= 4;
			}
			for (int i = 0; i < goodEffects.Length; i++)
			{
				if (goodEffects[i] == effect)
				{
					return true;
				}
			}
			return false;
		}

		private static string DescribeEffect(int effect)
		{
			return effect switch
			{
				0 => "Infinite stamina (4s)", 
				1 => "Faster climbing (5s)", 
				2 => "Low gravity (15s)", 
				3 => "Invincibility (10s)", 
				4 => "Cures hunger, injury and poison", 
				5 => "Spore explosion, launches you", 
				6 => "Blindness (60s)", 
				7 => "Fall damage", 
				8 => "Spores +25%", 
				9 => "Numbness (60s)", 
				_ => $"Unknown effect {effect}", 
			};
		}

		private void SetText(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				if ((Object)(object)_label != (Object)null && ((TMP_Text)_label).text.Length > 0)
				{
					((TMP_Text)_label).text = string.Empty;
				}
				return;
			}
			if ((Object)(object)_label == (Object)null)
			{
				_label = HudRoot.CreateLabel("SpongeQOL_MushroomInfo", (UiAnchor)1, FontSize?.Value ?? 15f);
				if ((Object)(object)_label == (Object)null)
				{
					return;
				}
				ModLog log = _log;
				if (log != null)
				{
					log.Debug((object)"Mushroom info label created.");
				}
			}
			if (((TMP_Text)_label).text != text)
			{
				((TMP_Text)_label).text = text;
			}
		}

		internal void Cleanup()
		{
			if ((Object)(object)_label != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_label).gameObject);
			}
			_label = null;
			_cached = null;
			_lastManager = null;
		}
	}
	public sealed class PushFeature : ModFeature
	{
		public const string RuleId = "AllowPushing";

		public const bool RuleDefault = true;

		private ConfigEntry<KeyCode> _key;

		private ConfigEntry<float> _force;

		private ConfigEntry<float> _range;

		private ConfigEntry<float> _cooldown;

		private ModLog _log;

		private float _nextPush;

		public override string Name => "Push";

		public override string Description => "Lets players shove each other. On by default; the host owns it for the whole lobby, because being pushed is not something the person falling gets to opt out of.";

		public override string Replaces => "boxofbiscuits97-PushMod";

		public override string Section => "Movement";

		internal static HostRuleSet Rules { get; set; }

		public void Configure(ConfigFile config)
		{
			_key = config.Bind<KeyCode>("Movement", "PushKey", (KeyCode)102, "Key that shoves the player you are looking at. Only works when the host allows pushing.");
			_force = config.Bind<float>("Movement", "PushForce", 18f, "Impulse strength. Around 18 is a firm shove; much higher launches people, which is funny once and then ends someone's run.");
			_range = config.Bind<float>("Movement", "PushRange", 3.5f, "How close you must be, in metres.");
			_cooldown = config.Bind<float>("Movement", "PushCooldown", 1f, "Seconds between pushes. Prevents holding the key from stunlocking someone and flooding the room with RPCs.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			log.Info((object)"Push ready; host rule 'AllowPushing' decides whether it is allowed.");
		}

		public void Tick()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (((ModFeature)this).IsActive && Rules != null && Rules.IsOn("AllowPushing"))
			{
				ConfigEntry<KeyCode> key = _key;
				if (Input.GetKeyDown((KeyCode)((key == null) ? 102 : ((int)key.Value))) && !(Time.time < _nextPush))
				{
					TryPush();
				}
			}
		}

		private void TryPush()
		{
			//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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead)
				{
					return;
				}
				Camera main = Camera.main;
				if ((Object)(object)main == (Object)null)
				{
					return;
				}
				Vector3 center = localCharacter.Center;
				Vector3 forward = ((Component)main).transform.forward;
				float num = Mathf.Clamp(_range?.Value ?? 3.5f, 1f, 10f);
				Character val = null;
				float num2 = 0.5f;
				foreach (Character allCharacter in Character.AllCharacters)
				{
					if ((Object)(object)allCharacter == (Object)null || (Object)(object)allCharacter == (Object)(object)localCharacter || allCharacter.isBot || (Object)(object)allCharacter.data == (Object)null || allCharacter.data.dead)
					{
						continue;
					}
					Vector3 val2 = allCharacter.Center - center;
					float magnitude = ((Vector3)(ref val2)).magnitude;
					if (!(magnitude > num) && !(magnitude < 0.01f))
					{
						float num3 = Vector3.Dot(forward, val2 / magnitude);
						if (num3 > num2)
						{
							num2 = num3;
							val = allCharacter;
						}
					}
				}
				if (!((Object)(object)val == (Object)null))
				{
					Push(val, forward);
				}
			}
			catch (Exception ex)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Exception("Push failed", ex);
				}
			}
		}

		private void Push(Character target, Vector3 direction)
		{
			//IL_0043: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_0066: 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_0080: Unknown result type (might be due to invalid IL or missing references)
			PhotonView val = target.refs?.view;
			if (!((Object)(object)val == (Object)null))
			{
				float num = Mathf.Clamp(_force?.Value ?? 18f, 1f, 60f);
				Vector3 val2 = direction + Vector3.up * 0.25f;
				Vector3 val3 = ((Vector3)(ref val2)).normalized * num;
				val.RPC("RPCA_AddForceAtPosition", (RpcTarget)0, new object[3] { val3, target.Center, 1.5f });
				_nextPush = Time.time + Mathf.Max(0.1f, _cooldown?.Value ?? 1f);
				ModLog log = _log;
				if (log != null)
				{
					log.Debug((object)("Pushed " + target.characterName + "."));
				}
			}
		}
	}
	public sealed class RopeCutFeature : ModFeature
	{
		public const string RuleId = "AllowRopeCutting";

		public const bool RuleDefault = true;

		private ConfigEntry<KeyCode> _key;

		private ConfigEntry<float> _range;

		private ConfigEntry<bool> _requireEmpty;

		private ModLog _log;

		private Rope _pending;

		private float _pendingUntil;

		public override string Name => "RopeCutting";

		public override string Description => "Lets players cut a rope down so a badly placed one is not permanent. Refuses a rope with someone on it unless that guard is disabled. The host can switch it off.";

		public override string Replaces => "xammen-RopeCut";

		public override string Section => "Movement";

		internal static HostRuleSet Rules { get; set; }

		public void Configure(ConfigFile config)
		{
			_key = config.Bind<KeyCode>("Movement", "RopeCutKey", (KeyCode)103, "Key that cuts the nearest rope. Only works when the host allows rope cutting.");
			_range = config.Bind<float>("Movement", "RopeCutRange", 3f, "How close to the rope you must be, in metres.");
			_requireEmpty = config.Bind<bool>("Movement", "RopeCutRequireEmpty", true, "Refuse to cut a rope somebody is currently climbing. Turning this off allows dropping other players, which is why the host rule exists at all.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			_log = log;
			log.Info((object)"Rope cutting ready; host rule 'AllowRopeCutting' decides whether it is allowed.");
		}

		public void Tick()
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (!((ModFeature)this).IsActive)
			{
				return;
			}
			CompletePendingCut();
			if (Rules != null && Rules.IsOn("AllowRopeCutting"))
			{
				ConfigEntry<KeyCode> key = _key;
				if (Input.GetKeyDown((KeyCode)((key == null) ? 103 : ((int)key.Value))))
				{
					TryCut();
				}
			}
		}

		private void CompletePendingCut()
		{
			if ((Object)(object)_pending == (Object)null)
			{
				return;
			}
			if (Time.time > _pendingUntil)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Debug((object)"Ownership transfer timed out; rope not cut.");
				}
				_pending = null;
				return;
			}
			PhotonView photonView = ((MonoBehaviourPun)_pending).photonView;
			if ((Object)(object)photonView == (Object)null)
			{
				_pending = null;
			}
			else if (photonView.IsMine)
			{
				Destroy(_pending);
				_pending = null;
			}
		}

		private void TryCut()
		{
			//IL_0055: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead)
				{
					return;
				}
				float num = Mathf.Clamp(_range?.Value ?? 3f, 1f, 10f);
				Vector3 center = localCharacter.Center;
				Rope val = null;
				float num2 = num;
				Rope[] array = Object.FindObjectsByType<Rope>((FindObjectsInactive)0, (FindObjectsSortMode)0);
				foreach (Rope val2 in array)
				{
					if ((Object)(object)val2 == (Object)null || !val2.IsActive())
					{
						continue;
					}
					ConfigEntry<bool> requireEmpty = _requireEmpty;
					if ((requireEmpty != null && !requireEmpty.Value) || val2.charactersClimbing == null || val2.charactersClimbing.Count <= 0)
					{
						float num3 = NearestSegmentDistance(val2, center);
						if (num3 < num2)
						{
							num2 = num3;
							val = val2;
						}
					}
				}
				if (!((Object)(object)val == (Object)null))
				{
					Cut(val);
				}
			}
			catch (Exception ex)
			{
				ModLog log = _log;
				if (log != null)
				{
					log.Exception("Rope cut failed", ex);
				}
			}
		}

		private static float NearestSegmentDistance(Rope rope, Vector3 point)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			List<Transform> ropeSegments = rope.GetRopeSegments();
			if (ropeSegments == null || ropeSegments.Count == 0)
			{
				return Vector3.Distance(((Component)rope).transform.position, point);
			}
			float num = float.MaxValue;
			foreach (Transform item in ropeSegments)
			{
				if (!((Object)(object)item == (Object)null))
				{
					float num2 = Vector3.Distance(item.position, point);
					if (num2 < num)
					{
						num = num2;
					}
				}
			}
			return num;
		}

		private void Cut(Rope rope)
		{
			PhotonView photonView = ((MonoBehaviourPun)rope).photonView;
			if ((Object)(object)photonView == (Object)null)
			{
				return;
			}
			if (photonView.IsMine)
			{
				Destroy(rope);
				return;
			}
			photonView.RequestOwnership();
			_pending = rope;
			_pendingUntil = Time.time + 2f;
			ModLog log = _log;
			if (log != null)
			{
				log.Debug((object)"Requested rope ownership before cutting.");
			}
		}

		private void Destroy(Rope rope)
		{
			try
			{
				PhotonNetwork.Destroy(((Component)rope).gameObject);
				ModLog log = _log;
				if (log != null)
				{
					log.Info((object)"Cut a rope.");
				}
			}
			catch (Exception ex)
			{
				ModLog log2 = _log;
				if (log2 != null)
				{
					log2.Exception("Could not destroy the rope", ex);
				}
			}
		}
	}
	public sealed class RopeSwingFeature : ModFeature
	{
		public const string RuleId = "AllowRopeSwinging";

		public const bool RuleDefault = true;

		private ConfigEntry<float> _maxAngle;

		internal const float VanillaAngle = 90f;

		public override string Name => "RopeSwinging";

		public override string Description => "Lets players swing on ropes rather than being dropped when the rope passes vertical. The host decides whether it is allowed for the lobby.";

		public override string Replaces => "lamiiasuu-RopeSwingingMod";

		public override string Section => "Movement";

		internal static HostRuleSet Rules { get; set; }

		internal static float SwingAngle { get; private set; } = 150f;

		public void Configure(ConfigFile config)
		{
			_maxAngle = config.Bind<float>("Movement", "RopeSwingMaxAngle", 150f, "How far past vertical a rope may swing before it drops you, in degrees. The game uses 90. Higher allows a fuller swing; beyond about 170 the rope can invert and look wrong.");
		}

		protected override void OnEnable(PatchHost patches, ModLog log)
		{
			SwingAngle = Mathf.Clamp(_maxAngle?.Value ?? 150f, 90f, 170f);
			if (!patches.PatchType(typeof(RopeSwingPatch)))
			{
				((ModFeature)this).Fail("could not patch CharacterRopeHandling.Update");
			}
			else
			{
				log.Info((object)"Rope swinging ready; host rule 'AllowRopeSwinging' decides whether it applies.");
			}
		}
	}
	[HarmonyPatch(typeof(CharacterRopeHandling), "Update")]
	internal static class RopeSwingPatch
	{
		[HarmonyPrefix]
		private static void Prefix(CharacterRopeHandling __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				bool flag = RopeSwingFeature.Rules != null && RopeSwingFeature.Rules.IsOn("AllowRopeSwinging");
				__instance.maxRopeAngle = (flag ? RopeSwingFeature.SwingAngle : 90f);
			}
		}
	}
	public sealed class TeammateStatsFeature : ModFeature
	{
		private sealed class Readout
		{
			public HudBar Bar;

			public TextMeshProUGUI Label;
		}

		internal static ConfigEntry<bool> ShowNames;

		internal static ConfigEntry<float> MaxDistance;

		private readonly Dictionary<Character, Readout> _readouts = new Dictionary<Character, Readout>();

		private readonly List<Character> _stale = new List<Character>();

		private ModLog _log;

		public override string Name => "TeammateStats";

		public override string Description => "Shows nearby teammates' stamina above their heads so you can tell who needs a rest.";

		public override string Replaces => "nickklmao-PeakStats";

		public override string Section => "Display";

		internal void Configure(ConfigFile config)
		{
			ShowNames = config.Bind<bool>("Display", "TeammateStats_ShowNames", true, "Shows each teammate's name above their stamina bar.");
			MaxDistance = config.Bind<float>("Display", "TeammateStats_MaxDistance", 30f, "Metres within which a teammate's stamina is shown.");
		}

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

		internal void Tick()
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || !HudRoot.IsReady)
			{
				HideAll();
				return;
			}
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				HideAll();
				return;
			}
			float num = MaxDistance?.Value ?? 30f;
			float num2 = num * num;
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters == null)
			{
				return;
			}
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Character val = allCharacters[i];
				if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)localCharacter || (Object)(object)val.data == (Object)null)
				{
					continue;
				}
				if (val.isBot || val.data.dead)
				{
					Hide(val);
					continue;
				}
				Vector3 head = val.Head;
				Vector3 val2 = head - localCharacter.Center;
				if (((Vector3)(ref val2)).sqrMagnitude > num2)
				{
					Hide(val);
				}
				else
				{
					Draw(val, head, main);
				}
			}
			PruneDestroyed();
		}

		private void Draw(Character character, Vector3 head, Camera camera)
		{
			//IL_0029: 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_0034: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			if (!_readouts.TryGetValue(character, out var value))
			{
				value = CreateReadout(character);
				if (value == null)
				{
					return;
				}
				_readouts[character] = value;
			}
			Vector3 val = head + Vector3.up * 0.45f;
			if (!HudRoot.PositionAtWorldPoint(value.Bar.Transform, val, camera))
			{
				SetVisible(value, visible: false);
				return;
			}
			if ((Object)(object)value.Label != (Object)null)
			{
				HudRoot.PositionAtWorldPoint(((TMP_Text)value.Label).rectTransform, val + Vector3.up * 0.25f, camera);
			}
			float maxStamina = character.GetMaxStamina();
			float currentStamina = character.data.currentStamina;
			float num = ((maxStamina > 0f) ? Mathf.Clamp01(currentStamina / maxStamina) : 0f);
			value.Bar.Value = num;
			value.Bar.Color = ColourFor(num);
			SetVisible(value, visible: true);
		}

		private static Color ColourFor(float fraction)
		{
			//IL_0017: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			if (fraction > 0.5f)
			{
				return new Color(0.5f, 0.85f, 0.5f);
			}
			if (fraction > 0.25f)
			{
				return new Color(0.9f, 0.8f, 0.4f);
			}
			return new Color(0.9f, 0.45f, 0.45f);
		}

		private Readout CreateReadout(Character character)
		{
			HudBar val = HudBar.Create("SpongeQOL_Stam_" + character.characterName, 90f, 7f);
			if (val == null)
			{
				return null;
			}
			TextMeshProUGUI val2 = null;
			if (ShowNames == null || ShowNames.Value)
			{
				val2 = HudRoot.CreateLabel("SpongeQOL_Name_" + character.characterName, (UiAnchor)5, 13f);
				if ((Object)(object)val2 != (Object)null)
				{
					((TMP_Text)val2).text = character.characterName;
				}
			}
			ModLog log = _log;
			if (log != null)
			{
				log.Debug((object)("Created stamina readout for '" + character.characterName + "'."));
			}
			return new Readout
			{
				Bar = val,
				Label = val2
			};
		}

		private void Hide(Character character)
		{
			if (_readouts.TryGetValue(character, out var value))
			{
				SetVisible(value, visible: false);
			}
		}

		private void HideAll()
		{
			foreach (KeyValuePair<Character, Readout> readout in _readouts)
			{
				SetVisible(readout.Value, visible: false);
			}
		}

		private static void SetVisible(Readout readout, bool visible)
		{
			HudBar bar = readout.Bar;
			if (bar != null)
			{
				bar.SetVisible(visible);
			}
			if ((Object)(object)readout.Label != (Object)null && ((Component)readout.Label).gameObject.activeSelf != visible)
			{
				((Component)readout.Label).gameObject.SetActive(visible);
			}
		}

		private void PruneDestroyed()
		{
			_stale.Clear();
			foreach (KeyValuePair<Character, Readout> readout in _readouts)
			{
				if ((Object)(object)readout.Key == (Object)null)
				{
					_stale.Add(readout.Key);
				}
			}
			foreach (Character item in _stale)
			{
				if (_readouts.TryGetValue(item, out var value))
				{
					DestroyReadout(value);
				}
				_readouts.Remove(item);
			}
		}

		private static void DestroyReadout(Readout readout)
		{
			HudBar bar = readout.Bar;
			if (bar != null)
			{
				bar.Destroy();
			}
			if ((Object)(object)readout.Label != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)readout.Label).gameObject);
			}
		}

		internal void Cleanup()
		{
			foreach (KeyValuePair<Character, Readout> readout in _readouts)
			{
				DestroyReadout(readout.Value);
			}
			_readouts.Clear();
		}
	}
}