Decompiled source of Southpaw Systems v1.0.0

BepInEx/plugins/SouthpawSystems/SouthpawSystems.dll

Decompiled 5 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
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 BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SouthpawSystems.Core;
using SouthpawSystems.Features;
using TMPro;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Networking;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SouthpawSystems")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+85357995d7b1b2b3624fb19c27357760db043227")]
[assembly: AssemblyProduct("SouthpawSystems")]
[assembly: AssemblyTitle("SouthpawSystems")]
[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 SouthpawSystems
{
	[BepInPlugin("kirk.southpawsystems", "Southpaw Systems", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "kirk.southpawsystems";

		public const string Version = "1.0.0";

		internal static ManualLogSource Log;

		internal static string Folder;

		private static readonly Feature[] Features = new Feature[21]
		{
			new SoundLimit(),
			new IndoorHazardPlaceholderFix(),
			new Dropship(),
			new DropshipMusic(),
			new FlashlightKey(),
			new WalkieKey(),
			new HideChat(),
			new HealthDisplay(),
			new HoarderBugSounds(),
			new ToiletPaperFix(),
			new NoCompanyFines(),
			new DeathCauses(),
			new CarryWeightFix(),
			new UtilitySlotPosition(),
			new Suits(),
			new GhostCam(),
			new CrewStats(),
			new ScrapBlacklist(),
			new EnemyBlacklist(),
			new NoIndoorFog(),
			new QuietCats()
		};

		private void Awake()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Folder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			ConfigFile config = new ConfigFile(Path.Combine(Paths.ConfigPath, "SouthpawSystems.cfg"), true, ((BaseUnityPlugin)this).Info.Metadata);
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			Feature[] features = Features;
			foreach (Feature feature in features)
			{
				try
				{
					feature.Load(config);
					(feature.IsEnabled ? list : list2).Add(feature.Name);
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"Feature '{feature.Name}' failed to load and is off: {arg}");
					list2.Add(feature.Name + " (failed)");
				}
			}
			Log.LogInfo((object)("Southpaw Systems 1.0.0. On: " + string.Join(", ", list)));
			if (list2.Count > 0)
			{
				Log.LogInfo((object)("Off: " + string.Join(", ", list2)));
			}
		}
	}
}
namespace SouthpawSystems.Features
{
	internal sealed class CarryWeightFix : Feature
	{
		private const float CheckInterval = 1f;

		private static float nextCheck;

		public override string Name => "CarryWeightFix";

		public override string Description => "Fixes players getting stuck slow because the game forgot to remove a destroyed item's weight.";

		[HarmonyPatch(typeof(PlayerControllerB), "Update")]
		[HarmonyPostfix]
		private static void Update(PlayerControllerB __instance)
		{
			if ((Object)(object)__instance != (Object)(object)LocalInput.LocalPlayer || Time.time < nextCheck)
			{
				return;
			}
			nextCheck = Time.time + 1f;
			if (__instance.isPlayerControlled && !__instance.isPlayerDead && !__instance.isGrabbingObjectAnimation)
			{
				float num = ExpectedWeight(__instance);
				if (__instance.carryWeight > num + 0.01f && !HasTulipSnake(__instance))
				{
					Plugin.Log.LogInfo((object)$"CarryWeightFix: weight was {__instance.carryWeight:0.00}, inventory adds up to {num:0.00}. Fixed.");
					__instance.carryWeight = num;
				}
			}
		}

		private static float ExpectedWeight(PlayerControllerB player)
		{
			float num = 1f;
			GrabbableObject[] itemSlots = player.ItemSlots;
			foreach (GrabbableObject val in itemSlots)
			{
				if ((Object)(object)val != (Object)null)
				{
					num += val.itemProperties.weight - 1f;
				}
			}
			if ((Object)(object)player.ItemOnlySlot != (Object)null)
			{
				num += player.ItemOnlySlot.itemProperties.weight - 1f;
			}
			return Mathf.Clamp(num, 1f, 10f);
		}

		private static bool HasTulipSnake(PlayerControllerB player)
		{
			FlowerSnakeEnemy[] array = Object.FindObjectsOfType<FlowerSnakeEnemy>();
			for (int i = 0; i < array.Length; i++)
			{
				if ((Object)(object)array[i].clingingToPlayer == (Object)(object)player)
				{
					return true;
				}
			}
			return false;
		}

		[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
		[HarmonyPostfix]
		private static void NewDay()
		{
			PlayerControllerB localPlayer = LocalInput.LocalPlayer;
			if ((Object)(object)localPlayer != (Object)null && localPlayer.isMovementHindered != 0)
			{
				Plugin.Log.LogInfo((object)$"CarryWeightFix: clearing leftover movement slowdown ({localPlayer.isMovementHindered}).");
				localPlayer.isMovementHindered = 0;
				localPlayer.hinderedMultiplier = 1f;
			}
		}
	}
	internal sealed class CrewStats : Feature
	{
		[Serializable]
		private class Entry
		{
			public string id;

			public string name;

			public int scrap;

			public int value;

			public int deaths;

			public long steps;

			public bool played;
		}

		[Serializable]
		private class Table
		{
			public List<Entry> players = new List<Entry>();
		}

		private static class Screen
		{
			private const float PixelWidth = 1400f;

			private const float PixelHeight = 780f;

			private const float Padding = 44f;

			private static readonly Color Background = new Color(0.02f, 0.03f, 0.02f, 1f);

			private static readonly Color Bezel = new Color(0.12f, 0.12f, 0.12f, 1f);

			private const string Green = "#3CFF2E";

			private const string Dim = "#1E7A18";

			private const string Gold = "#FFD23C";

			private const string Red = "#FF4B3C";

			private static Transform screenRoot;

			private static TextMeshProUGUI title;

			private static TextMeshProUGUI footer;

			private static TextMeshProUGUI rank;

			private static TextMeshProUGUI name;

			private static TextMeshProUGUI scrap;

			private static TextMeshProUGUI value;

			private static TextMeshProUGUI deaths;

			private static TextMeshProUGUI steps;

			public static bool Frozen { get; private set; }

			public static void Place()
			{
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0054: 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_0078: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)screenRoot == (Object)null))
				{
					screenRoot.localPosition = new Vector3(screenX.Value, screenY.Value, screenZ.Value);
					screenRoot.localRotation = Quaternion.Euler(0f, yaw.Value, 0f);
					screenRoot.localScale = Vector3.one * (width.Value / 1400f);
				}
			}

			public static void Build(StartOfRound round)
			{
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Expected O, but got Unknown
				//IL_008d: Unknown result type (might be due to invalid IL or missing references)
				//IL_009c: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
				Terminal val = Object.FindObjectOfType<Terminal>();
				TMP_FontAsset font = (((Object)(object)val != (Object)null) ? val.screenText.textComponent.font : null);
				int layer = (((Object)(object)val != (Object)null) ? ((Component)val.terminalUIScreen).gameObject.layer : 0);
				GameObject val2 = new GameObject("SouthpawSystems.CrewStatsScreen", new Type[1] { typeof(RectTransform) });
				val2.layer = layer;
				val2.transform.SetParent(round.elevatorTransform, false);
				screenRoot = val2.transform;
				Place();
				((RectTransform)val2.transform).sizeDelta = new Vector2(1400f, 780f);
				val2.AddComponent<Canvas>().renderMode = (RenderMode)2;
				Panel(val2, "Bezel", Bezel, 0f);
				Panel(val2, "Glass", Background, 14f);
				title = Text(val2, font, "Title", 0f, 1f, 22f, 70f, 58f, (TextAlignmentOptions)258);
				footer = Text(val2, font, "Footer", 0f, 1f, 716f, 40f, 24f, (TextAlignmentOptions)258);
				SetFrozen(!round.inShipPhase);
				rank = Text(val2, font, "Rank", 0f, 0.05f, 118f, 560f, 38f, (TextAlignmentOptions)260);
				name = Text(val2, font, "Name", 0.07f, 0.42f, 118f, 560f, 38f, (TextAlignmentOptions)257);
				scrap = Text(val2, font, "Scrap", 0.42f, 0.55f, 118f, 560f, 38f, (TextAlignmentOptions)260);
				value = Text(val2, font, "Value", 0.55f, 0.72f, 118f, 560f, 38f, (TextAlignmentOptions)260);
				deaths = Text(val2, font, "Deaths", 0.72f, 0.85f, 118f, 560f, 38f, (TextAlignmentOptions)260);
				steps = Text(val2, font, "Steps", 0.85f, 1f, 118f, 560f, 38f, (TextAlignmentOptions)260);
				Refresh(table);
				LogPlacement(round, val2, layer);
			}

			private static void LogPlacement(StartOfRound round, GameObject root, int layer)
			{
				//IL_0052: 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)
				Transform elevatorTransform = round.elevatorTransform;
				Camera val = GameNetworkManager.Instance?.localPlayerController?.gameplayCamera;
				bool flag = (Object)(object)val == (Object)null || (val.cullingMask & (1 << layer)) != 0;
				Plugin.Log.LogInfo((object)($"CrewStats: screen at ship {root.transform.localPosition} (world {root.transform.position}), " + string.Format("parent '{0}', layer {1} '{2}'{3}.", ((Object)elevatorTransform).name, layer, LayerMask.LayerToName(layer), flag ? "" : " NOT RENDERED by the player camera")));
			}

			private static void Panel(GameObject root, string label, Color color, float inset)
			{
				//IL_0014: 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_0025: 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)
				//IL_003d: 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_0043: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_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_0069: 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)
				GameObject val = new GameObject(label, new Type[1] { typeof(RectTransform) })
				{
					layer = root.layer
				};
				val.transform.SetParent(root.transform, false);
				RectTransform val2 = (RectTransform)val.transform;
				val2.anchorMin = Vector2.zero;
				val2.anchorMax = Vector2.one;
				val2.offsetMin = new Vector2(inset, inset);
				val2.offsetMax = new Vector2(0f - inset, 0f - inset);
				((Graphic)val.AddComponent<Image>()).color = color;
			}

			private static TextMeshProUGUI Text(GameObject root, TMP_FontAsset font, string label, float left, float right, float top, float height, float size, TextAlignmentOptions alignment)
			{
				//IL_0014: 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_0025: 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)
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0048: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: 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_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_0072: 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)
				//IL_0089: Unknown result type (might be due to invalid IL or missing references)
				//IL_009b: 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_00e5: Unknown result type (might be due to invalid IL or missing references)
				GameObject val = new GameObject(label, new Type[1] { typeof(RectTransform) })
				{
					layer = root.layer
				};
				val.transform.SetParent(root.transform, false);
				RectTransform val2 = (RectTransform)val.transform;
				float num = 1312f;
				Vector2 val3 = default(Vector2);
				((Vector2)(ref val3))..ctor(0f, 1f);
				val2.anchorMax = val3;
				val2.anchorMin = val3;
				val2.pivot = new Vector2(0f, 1f);
				val2.anchoredPosition = new Vector2(44f + left * num, 0f - top);
				val2.sizeDelta = new Vector2((right - left) * num, height);
				TextMeshProUGUI val4 = val.AddComponent<TextMeshProUGUI>();
				if ((Object)(object)font != (Object)null)
				{
					((TMP_Text)val4).font = font;
				}
				((TMP_Text)val4).fontSize = size;
				((TMP_Text)val4).alignment = alignment;
				((TMP_Text)val4).enableWordWrapping = false;
				((TMP_Text)val4).overflowMode = (TextOverflowModes)1;
				((TMP_Text)val4).lineSpacing = 12f;
				((Graphic)val4).color = Color.white;
				return val4;
			}

			public static void SetFrozen(bool frozen)
			{
				Frozen = frozen;
				if (!((Object)(object)footer == (Object)null))
				{
					((TMP_Text)footer).text = (frozen ? "<color=#FFD23C>Frozen while on the moon. Updates when the ship leaves.</color>" : "<color=#1E7A18>Scrap counts for whoever found it, once the ship leaves with it.</color>");
				}
			}

			public static void Refresh(Table data)
			{
				if (!((Object)(object)title == (Object)null))
				{
					((TMP_Text)title).text = "<color=#3CFF2E><b>CREW STATS</b></color>";
					HashSet<string> online = new HashSet<string>(from id in StartOfRound.Instance.allPlayerScripts.Where((PlayerControllerB p) => p.isPlayerControlled || p.isPlayerDead).Select(IdOf)
						where id != null
						select id);
					List<Entry> list = (from e in data.players
						where e.played || online.Contains(e.id)
						orderby e.value descending, e.scrap descending, e.deaths
						select e).Take(Mathf.Max(1, maxRows.Value)).ToList();
					int num = ((list.Count > 0) ? list.Max((Entry e) => e.scrap) : 0);
					int num2 = ((list.Count > 0) ? list.Max((Entry e) => e.value) : 0);
					int num3 = ((list.Count > 0) ? list.Max((Entry e) => e.deaths) : 0);
					long num4 = ((list.Count > 0) ? list.Max((Entry e) => e.steps) : 0);
					StringBuilder stringBuilder = new StringBuilder("<color=#1E7A18>#</color>\n");
					StringBuilder stringBuilder2 = new StringBuilder("<color=#1E7A18>NAME</color>\n");
					StringBuilder stringBuilder3 = new StringBuilder("<color=#1E7A18>SCRAP</color>\n");
					StringBuilder stringBuilder4 = new StringBuilder("<color=#1E7A18>VALUE</color>\n");
					StringBuilder stringBuilder5 = new StringBuilder("<color=#1E7A18>DEATHS</color>\n");
					StringBuilder stringBuilder6 = new StringBuilder("<color=#1E7A18>STEPS</color>\n");
					for (int num5 = 0; num5 < list.Count; num5++)
					{
						Entry entry = list[num5];
						string text = (online.Contains(entry.id) ? "#3CFF2E" : "#1E7A18");
						stringBuilder.Append(Cell((num5 + 1).ToString(), text, leader: false));
						stringBuilder2.Append(Cell(Clean(entry.name), text, leader: false));
						stringBuilder3.Append(Cell(Number(entry.scrap), text, entry.scrap > 0 && entry.scrap == num));
						stringBuilder4.Append(Cell("$" + Number(entry.value), text, entry.value > 0 && entry.value == num2));
						stringBuilder5.Append(Cell(Number(entry.deaths), (entry.deaths > 0 && entry.deaths == num3) ? "#FF4B3C" : text, leader: false));
						stringBuilder6.Append(Cell(Number(entry.steps), text, entry.steps > 0 && entry.steps == num4));
					}
					if (list.Count == 0)
					{
						stringBuilder2.Append("<color=#3CFF2E>No stats yet. Go get some scrap.</color>");
					}
					((TMP_Text)rank).text = stringBuilder.ToString();
					((TMP_Text)name).text = stringBuilder2.ToString();
					((TMP_Text)scrap).text = stringBuilder3.ToString();
					((TMP_Text)value).text = stringBuilder4.ToString();
					((TMP_Text)deaths).text = stringBuilder5.ToString();
					((TMP_Text)steps).text = stringBuilder6.ToString();
				}
			}

			private static string Cell(string text, string color, bool leader)
			{
				return "<color=" + (leader ? "#FFD23C" : color) + ">" + text + "</color>\n";
			}

			private static string Number(long n)
			{
				return n.ToString("N0", CultureInfo.InvariantCulture);
			}

			private static string Clean(string name)
			{
				name = (string.IsNullOrEmpty(name) ? "?" : name.Replace("<", "").Replace(">", ""));
				if (name.Length <= 16)
				{
					return name;
				}
				return name.Substring(0, 15) + ".";
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__ReceiveSteps;

			public static HandleNamedMessageDelegate <1>__ReceiveSnapshot;
		}

		private const string SaveKey = "SouthpawSystems.CrewStats";

		private const string SnapshotMessage = "SouthpawSystems.CrewStats.Snapshot";

		private const string StepsMessage = "SouthpawSystems.CrewStats.Steps";

		private const float BroadcastInterval = 2f;

		private const float StepReportInterval = 5f;

		private const int MaxStepsPerReport = 100;

		private static ConfigEntry<float> screenX;

		private static ConfigEntry<float> screenY;

		private static ConfigEntry<float> screenZ;

		private static ConfigEntry<float> yaw;

		private static ConfigEntry<float> width;

		private static ConfigEntry<int> maxRows;

		private static Table table = new Table();

		private static bool tableChanged;

		private static float screenTimer;

		private static readonly Dictionary<GrabbableObject, Entry> foundBy = new Dictionary<GrabbableObject, Entry>();

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

		private static bool needsBroadcast;

		private static float broadcastTimer;

		private static int pendingSteps;

		private static float stepTimer;

		private static StartOfRound startedFor;

		private static ConfigFile configFile;

		private static DateTime configWritten;

		private static float configCheckTimer;

		public override string Name => "CrewStats";

		public override string Description => "A leaderboard screen on the ship: scrap brought home (credited to whoever found it), its value, deaths and steps. Everyone needs this on; the host keeps the score.";

		private static bool IsServer
		{
			get
			{
				if ((Object)(object)NetworkManager.Singleton != (Object)null)
				{
					return NetworkManager.Singleton.IsServer;
				}
				return false;
			}
		}

		protected override void BindConfig(ConfigFile config)
		{
			configFile = config;
			configWritten = File.GetLastWriteTimeUtc(config.ConfigFilePath);
			screenX = config.Bind<float>(Name, "ScreenX", -0.85f, "Where the screen hangs, in meters in the ship's own coordinates. The default is the wall across from the charging station, left of the light switch, out in front of the wall's ribs. Position and size update live when you save this file, no restart needed.\nX: towards the front of the ship (the lever).");
			screenY = config.Bind<float>(Name, "ScreenY", 2.2f, "Y: height of the screen's center above the floor.");
			screenZ = config.Bind<float>(Name, "ScreenZ", -4.6f, "Z: across the ship. The wall is at about -4.22 and its ribs stick out past -4.35; smaller numbers move the screen out from it.");
			yaw = config.Bind<float>(Name, "ScreenRotation", 0f, "Turns the screen around the vertical axis, in degrees.");
			width = config.Bind<float>(Name, "ScreenWidth", 2.4f, "Screen width in meters. The height follows.");
			maxRows = config.Bind<int>(Name, "MaxRows", 10, "How many players the board lists, best first.");
		}

		[HarmonyPatch(typeof(StartOfRound), "Start")]
		[HarmonyPostfix]
		private static void LobbyStarted(StartOfRound __instance)
		{
			//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_009f: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			startedFor = __instance;
			foundBy.Clear();
			diedThisRound.Clear();
			pendingSteps = 0;
			table = (IsServer ? Load() : new Table());
			tableChanged = true;
			needsBroadcast = true;
			CustomMessagingManager customMessagingManager = NetworkManager.Singleton.CustomMessagingManager;
			if (IsServer)
			{
				object obj = <>O.<0>__ReceiveSteps;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = ReceiveSteps;
					<>O.<0>__ReceiveSteps = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("SouthpawSystems.CrewStats.Steps", (HandleNamedMessageDelegate)obj);
			}
			else
			{
				object obj2 = <>O.<1>__ReceiveSnapshot;
				if (obj2 == null)
				{
					HandleNamedMessageDelegate val2 = ReceiveSnapshot;
					<>O.<1>__ReceiveSnapshot = val2;
					obj2 = (object)val2;
				}
				customMessagingManager.RegisterNamedMessageHandler("SouthpawSystems.CrewStats.Snapshot", (HandleNamedMessageDelegate)obj2);
				SendSteps(0);
			}
			Screen.Build(__instance);
		}

		[HarmonyPatch(typeof(StartOfRound), "OnDestroy")]
		[HarmonyPrefix]
		private static void LobbyClosed()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager obj = ((singleton != null) ? singleton.CustomMessagingManager : null);
			if (obj != null)
			{
				obj.UnregisterNamedMessageHandler("SouthpawSystems.CrewStats.Steps");
			}
			if (obj != null)
			{
				obj.UnregisterNamedMessageHandler("SouthpawSystems.CrewStats.Snapshot");
			}
			foundBy.Clear();
		}

		[HarmonyPatch(typeof(StartOfRound), "Update")]
		[HarmonyPostfix]
		private static void Tick(StartOfRound __instance)
		{
			if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsListening)
			{
				return;
			}
			if ((Object)(object)startedFor != (Object)(object)__instance)
			{
				Plugin.Log.LogWarning((object)"CrewStats: StartOfRound.Start didn't reach us (another mod cut it short); setting up now.");
				LobbyStarted(__instance);
			}
			stepTimer += Time.deltaTime;
			if (stepTimer >= 5f)
			{
				stepTimer = 0f;
				FlushSteps();
			}
			if (IsServer)
			{
				broadcastTimer += Time.deltaTime;
				if (broadcastTimer >= 2f)
				{
					broadcastTimer = 0f;
					PlayerControllerB[] allPlayerScripts = __instance.allPlayerScripts;
					foreach (PlayerControllerB val in allPlayerScripts)
					{
						if (val.isPlayerControlled || val.isPlayerDead)
						{
							EntryFor(val);
						}
					}
					if (needsBroadcast)
					{
						Broadcast();
					}
				}
			}
			configCheckTimer += Time.deltaTime;
			if (configCheckTimer >= 1f)
			{
				configCheckTimer = 0f;
				DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(configFile.ConfigFilePath);
				if (lastWriteTimeUtc != configWritten)
				{
					configWritten = lastWriteTimeUtc;
					configFile.Reload();
					Screen.Place();
				}
			}
			bool flag = !__instance.inShipPhase;
			if (flag != Screen.Frozen)
			{
				Screen.SetFrozen(flag);
				tableChanged = true;
			}
			if (!flag)
			{
				screenTimer += Time.deltaTime;
				if (tableChanged || screenTimer >= 2f)
				{
					tableChanged = false;
					screenTimer = 0f;
					Screen.Refresh(table);
				}
			}
		}

		private static Table Load()
		{
			try
			{
				string text = ES3.Load<string>("SouthpawSystems.CrewStats", GameNetworkManager.Instance.currentSaveFileName, "");
				if (!string.IsNullOrEmpty(text))
				{
					return JsonUtility.FromJson<Table>(text) ?? new Table();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("CrewStats: couldn't read the saved stats, starting fresh. " + ex.Message));
			}
			return new Table();
		}

		[HarmonyPatch(typeof(GameNetworkManager), "SaveGame")]
		[HarmonyPostfix]
		private static void Save(GameNetworkManager __instance)
		{
			if (__instance.isHostingGame && !((Object)(object)StartOfRound.Instance == (Object)null) && !StartOfRound.Instance.isChallengeFile)
			{
				FlushSteps();
				Table table = new Table
				{
					players = CrewStats.table.players.Where((Entry e) => e.played).ToList()
				};
				ES3.Save<string>("SouthpawSystems.CrewStats", JsonUtility.ToJson((object)table), __instance.currentSaveFileName);
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "GrabObjectClientRpc")]
		[HarmonyPostfix]
		private static void Grabbed(PlayerControllerB __instance, bool grabValidated, NetworkObjectReference grabbedObject)
		{
			NetworkObject val = default(NetworkObject);
			if (IsServer && grabValidated && ((NetworkObjectReference)(ref grabbedObject)).TryGet(ref val, (NetworkManager)null))
			{
				MarkFound(((Component)val).GetComponentInChildren<GrabbableObject>(), __instance);
			}
		}

		[HarmonyPatch(typeof(BeltBagItem), "TryAddObjectToBagClientRpc")]
		[HarmonyPostfix]
		private static void Bagged(NetworkObjectReference netObjectRef, int playerWhoAdded)
		{
			NetworkObject val = default(NetworkObject);
			if (IsServer && ((NetworkObjectReference)(ref netObjectRef)).TryGet(ref val, (NetworkManager)null))
			{
				PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
				if (playerWhoAdded >= 0 && playerWhoAdded < allPlayerScripts.Length)
				{
					MarkFound(((Component)val).GetComponentInChildren<GrabbableObject>(), allPlayerScripts[playerWhoAdded]);
				}
			}
		}

		private static void MarkFound(GrabbableObject item, PlayerControllerB player)
		{
			if (!((Object)(object)item == (Object)null) && !foundBy.ContainsKey(item) && CountsAsScrap(item))
			{
				Entry entry = EntryFor(player);
				if (entry != null)
				{
					foundBy[item] = entry;
				}
			}
		}

		[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
		[HarmonyPrefix]
		private static void ShipLeft(RoundManager __instance, bool despawnAllItems)
		{
			if (!((NetworkBehaviour)__instance).IsServer)
			{
				return;
			}
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if (val.isPlayerControlled || val.isPlayerDead)
				{
					Entry entry = EntryFor(val);
					if (entry != null && !entry.played)
					{
						entry.played = true;
						needsBroadcast = (tableChanged = true);
					}
				}
			}
			if (!despawnAllItems && !StartOfRound.Instance.allPlayersDead)
			{
				HashSet<GrabbableObject> hashSet = new HashSet<GrabbableObject>();
				BeltBagItem[] array = Object.FindObjectsByType<BeltBagItem>((FindObjectsSortMode)0);
				foreach (BeltBagItem val2 in array)
				{
					if (((GrabbableObject)val2).isInShipRoom || ((GrabbableObject)val2).isHeld)
					{
						hashSet.UnionWith(val2.objectsInBag);
					}
				}
				foreach (var (val4, entry3) in foundBy)
				{
					if (!((Object)(object)val4 == (Object)null) && CountsAsScrap(val4) && !val4.deactivated && (val4.isInShipRoom || val4.isHeld || hashSet.Contains(val4)))
					{
						entry3.scrap++;
						entry3.value += val4.scrapValue;
						needsBroadcast = (tableChanged = true);
					}
				}
			}
			foundBy.Clear();
		}

		private static bool CountsAsScrap(GrabbableObject item)
		{
			if ((Object)(object)item.itemProperties != (Object)null && item.itemProperties.isScrap && !item.scrapPersistedThroughRounds && !(item is RagdollGrabbableObject))
			{
				return !(item is KeyItem);
			}
			return false;
		}

		[HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")]
		[HarmonyPostfix]
		private static void Died(int playerId)
		{
			if (!IsServer)
			{
				return;
			}
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			if (playerId >= 0 && playerId < allPlayerScripts.Length && allPlayerScripts[playerId].isPlayerDead && diedThisRound.Add(playerId))
			{
				Entry entry = EntryFor(allPlayerScripts[playerId]);
				if (entry != null)
				{
					entry.deaths++;
					needsBroadcast = (tableChanged = true);
				}
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
		[HarmonyPostfix]
		private static void NewDay()
		{
			diedThisRound.Clear();
		}

		[HarmonyPatch(typeof(PlayerControllerB), "PlayFootstepSound")]
		[HarmonyPostfix]
		private static void Footstep(PlayerControllerB __instance)
		{
			if (((NetworkBehaviour)__instance).IsOwner && __instance.isPlayerControlled && (Object)(object)__instance == (Object)(object)LocalInput.LocalPlayer)
			{
				pendingSteps++;
			}
		}

		private static void FlushSteps()
		{
			if (pendingSteps != 0 && !((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsListening)
			{
				if (IsServer)
				{
					AddSteps(LocalInput.LocalPlayer, pendingSteps);
				}
				else
				{
					SendSteps(pendingSteps);
				}
				pendingSteps = 0;
			}
		}

		private static void AddSteps(PlayerControllerB player, int steps)
		{
			Entry entry = (((Object)(object)player == (Object)null) ? null : EntryFor(player));
			if (entry != null)
			{
				entry.steps += steps;
				needsBroadcast = (tableChanged = true);
			}
		}

		private static void SendSteps(int steps)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: 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)
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(4, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref steps, default(ForPrimitives));
				NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("SouthpawSystems.CrewStats.Steps", 0uL, val, (NetworkDelivery)3);
			}
			finally
			{
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void ReceiveSteps(ulong sender, FastBufferReader reader)
		{
			//IL_0006: 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)
			int num = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
			int value;
			if (num == 0)
			{
				SendSnapshot(new ulong[1] { sender });
			}
			else if (StartOfRound.Instance.ClientPlayerList.TryGetValue(sender, out value))
			{
				AddSteps(StartOfRound.Instance.allPlayerScripts[value], Mathf.Clamp(num, 0, 100));
			}
		}

		private static void Broadcast()
		{
			needsBroadcast = false;
			ulong[] array = NetworkManager.Singleton.ConnectedClientsIds.Where((ulong id) => id != 0).ToArray();
			if (array.Length != 0)
			{
				SendSnapshot(array);
			}
		}

		private static void SendSnapshot(IReadOnlyList<ulong> clients)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			string text = JsonUtility.ToJson((object)table);
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(FastBufferWriter.GetWriteSize(text, false), (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe(text, false);
				NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("SouthpawSystems.CrewStats.Snapshot", clients, val, (NetworkDelivery)4);
			}
			finally
			{
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void ReceiveSnapshot(ulong sender, FastBufferReader reader)
		{
			string text = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false);
			table = JsonUtility.FromJson<Table>(text) ?? new Table();
			tableChanged = true;
		}

		private static Entry EntryFor(PlayerControllerB player)
		{
			string id = IdOf(player);
			if (id == null)
			{
				return null;
			}
			Entry entry = table.players.Find((Entry e) => e.id == id);
			if (entry == null)
			{
				entry = new Entry
				{
					id = id
				};
				table.players.Add(entry);
				needsBroadcast = (tableChanged = true);
			}
			if (entry.name != player.playerUsername)
			{
				entry.name = player.playerUsername;
				needsBroadcast = (tableChanged = true);
			}
			return entry;
		}

		private static string IdOf(PlayerControllerB player)
		{
			if (GameNetworkManager.Instance.disableSteam)
			{
				if (!string.IsNullOrEmpty(player.playerUsername))
				{
					return "lan:" + player.playerUsername;
				}
				return null;
			}
			if (player.playerSteamId != 0L)
			{
				return player.playerSteamId.ToString(CultureInfo.InvariantCulture);
			}
			return null;
		}
	}
	internal sealed class DeathCauses : Feature
	{
		private const float KillerRange = 6f;

		private static readonly Dictionary<int, string> deaths = new Dictionary<int, string>();

		public override string Name => "DeathCauses";

		public override string Description => "Shows how each player died on the results screen, including which monster did it.";

		[HarmonyPostfix]
		[HarmonyPatch(typeof(PlayerControllerB), "KillPlayer")]
		[HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")]
		private static void Died(PlayerControllerB __instance)
		{
			//IL_0029: 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 (__instance.isPlayerDead && !deaths.ContainsKey((int)__instance.playerClientId))
			{
				deaths[(int)__instance.playerClientId] = Describe(__instance.causeOfDeath, FindKiller(((Component)__instance).transform.position));
			}
		}

		[HarmonyPatch(typeof(HUDManager), "FillEndGameStats")]
		[HarmonyPostfix]
		private static void ResultsScreen(HUDManager __instance)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			for (int i = 0; i < allPlayerScripts.Length && i < __instance.statsUIElements.playerNotesText.Length; i++)
			{
				if (allPlayerScripts[i].isPlayerDead)
				{
					string value;
					string text = (((int)allPlayerScripts[i].causeOfDeath == 10) ? "Left behind" : (deaths.TryGetValue((int)allPlayerScripts[i].playerClientId, out value) ? value : Describe(allPlayerScripts[i].causeOfDeath, null)));
					TextMeshProUGUI obj = __instance.statsUIElements.playerNotesText[i];
					((TMP_Text)obj).text = ((TMP_Text)obj).text + "* " + text + "\n";
				}
			}
			deaths.Clear();
		}

		[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
		[HarmonyPostfix]
		private static void NewDay()
		{
			deaths.Clear();
		}

		private static string FindKiller(Vector3 position)
		{
			//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)
			EnemyAI val = null;
			float num = 6f;
			foreach (EnemyAI spawnedEnemy in RoundManager.Instance.SpawnedEnemies)
			{
				if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead)
				{
					float num2 = Vector3.Distance(((Component)spawnedEnemy).transform.position, position);
					if (num2 < num)
					{
						num = num2;
						val = spawnedEnemy;
					}
				}
			}
			if (!((Object)(object)val == (Object)null))
			{
				return EnemyName(val);
			}
			return null;
		}

		private static string EnemyName(EnemyAI enemy)
		{
			ScanNodeProperties componentInChildren = ((Component)enemy).GetComponentInChildren<ScanNodeProperties>();
			if ((Object)(object)componentInChildren != (Object)null && !string.IsNullOrWhiteSpace(componentInChildren.headerText))
			{
				return componentInChildren.headerText;
			}
			if (!((Object)(object)enemy.enemyType != (Object)null))
			{
				return ((Object)enemy).name;
			}
			return enemy.enemyType.enemyName;
		}

		private unsafe static string Describe(CauseOfDeath cause, string killer)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected I4, but got Unknown
			string text = (int)cause switch
			{
				1 => "Bludgeoned", 
				2 => "Fell to their death", 
				3 => "Blown up", 
				4 => "Strangled", 
				5 => "Suffocated", 
				6 => "Mauled", 
				7 => "Shot", 
				8 => "Crushed", 
				9 => "Drowned", 
				10 => "Left behind", 
				11 => "Electrocuted", 
				12 => "Kicked", 
				13 => "Burned", 
				14 => "Stabbed", 
				15 => "Caught in a fan", 
				16 => "Crashed", 
				17 => "Snipped", 
				18 => "Scratched", 
				0 => "Killed", 
				_ => ((object)(*(CauseOfDeath*)(&cause))/*cast due to .constrained prefix*/).ToString(), 
			};
			if (killer != null)
			{
				return text + " (" + killer + ")";
			}
			return text;
		}
	}
	internal sealed class Dropship : Feature
	{
		private static ConfigEntry<int> deliverySeconds;

		private static ConfigEntry<int> staySeconds;

		private static ConfigEntry<int> leaveAfterOpenSeconds;

		private static ConfigEntry<bool> countDuringLanding;

		private static ConfigEntry<bool> lateOrdersJoin;

		private const float ArriveAt = 40f;

		private const float LeaveAt = 30f;

		private const int DefaultMaxItems = 30;

		private static Func<int> giLimit;

		public override string Name => "Dropship";

		public override string Description => "Item dropship comes right away and waits for you to open it. Only the host's settings matter.";

		private static int MaxItems()
		{
			if (giLimit == null)
			{
				ConfigEntry<int> entry = AccessTools.TypeByName("GeneralImprovements.Plugin")?.GetProperty("DropShipItemLimit")?.GetValue(null) as ConfigEntry<int>;
				giLimit = ((entry != null) ? ((Func<int>)(() => entry.Value)) : ((Func<int>)(() => 30)));
			}
			return giLimit();
		}

		protected override void BindConfig(ConfigFile config)
		{
			deliverySeconds = config.Bind<int>(Name, "DeliverySeconds", 0, "Seconds from ordering until the dropship heads down. 0 = right away. Vanilla: 40 (20 for the first order).");
			staySeconds = config.Bind<int>(Name, "StaySeconds", 0, "Seconds the dropship waits after landing if nobody opens it. 0 = waits until someone opens it (it still leaves with your ship at the end of the day). Vanilla: 30.");
			leaveAfterOpenSeconds = config.Bind<int>(Name, "LeaveAfterOpenSeconds", 3, "Seconds the dropship stays after its doors are opened.");
			countDuringLanding = config.Bind<bool>(Name, "CountDuringShipLanding", true, "Start the delivery countdown while your ship is still landing on the moon.");
			lateOrdersJoin = config.Bind<bool>(Name, "LateOrdersJoinDelivery", true, "Items bought after the dropship left orbit, but before its doors open, ride along on the same trip.");
		}

		[HarmonyPatch(typeof(ItemDropship), "Update")]
		[HarmonyPrefix]
		private static void Update(ItemDropship __instance)
		{
			if (!((NetworkBehaviour)__instance).IsServer)
			{
				return;
			}
			Terminal terminalScript = __instance.terminalScript;
			bool flag = terminalScript.orderedItemsFromTerminal.Count > 0 || terminalScript.orderedVehicleFromTerminal != -1;
			if (!__instance.deliveringOrder)
			{
				if (flag)
				{
					__instance.playersFirstOrder = false;
					__instance.shipTimer = Mathf.Max(__instance.shipTimer, 40f - (float)deliverySeconds.Value);
					if (countDuringLanding.Value && !StartOfRound.Instance.shipHasLanded)
					{
						__instance.shipTimer += Time.deltaTime;
					}
				}
				return;
			}
			if (staySeconds.Value <= 0 && __instance.shipLanded && !__instance.shipDoorsOpened)
			{
				__instance.shipTimer = 0f;
			}
			if (lateOrdersJoin.Value && !__instance.deliveringVehicle && !__instance.shipDoorsOpened)
			{
				List<int> orderedItemsFromTerminal = terminalScript.orderedItemsFromTerminal;
				while (orderedItemsFromTerminal.Count > 0 && __instance.itemsToDeliver.Count < MaxItems())
				{
					__instance.itemsToDeliver.Add(orderedItemsFromTerminal[0]);
					orderedItemsFromTerminal.RemoveAt(0);
				}
			}
		}

		[HarmonyTranspiler]
		[HarmonyPatch(typeof(Terminal), "SyncBoughtItemsWithServer")]
		[HarmonyPatch(typeof(Terminal), "BuyItemsServerRpc")]
		private static IEnumerable<CodeInstruction> RaiseOrderLimit(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
		{
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (instruction.opcode == OpCodes.Ldc_I4_S && instruction.operand is sbyte b && b == 12)
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = AccessTools.Method(typeof(Dropship), "MaxItems", (Type[])null, (Type[])null);
					replaced++;
				}
				yield return instruction;
			}
			if (replaced != 1)
			{
				Plugin.Log.LogWarning((object)$"Dropship: expected one 12-item limit in Terminal.{__originalMethod.Name}, found {replaced}.");
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ItemDropship), "ShipLandedAnimationEvent")]
		[HarmonyPatch(typeof(ItemDropship), "ShipLandedInAnimation")]
		private static void Landed(ItemDropship __instance)
		{
			if (((NetworkBehaviour)__instance).IsServer && !__instance.deliveringVehicle && staySeconds.Value > 0)
			{
				__instance.shipTimer = 30f - (float)staySeconds.Value;
			}
		}

		[HarmonyPatch(typeof(ItemDropship), "OpenShipDoorsOnServer")]
		[HarmonyPostfix]
		private static void DoorsOpened(ItemDropship __instance)
		{
			__instance.shipTimer = Mathf.Max(__instance.shipTimer, 30f - (float)leaveAfterOpenSeconds.Value);
		}
	}
	internal sealed class DropshipMusic : Feature
	{
		internal static ConfigEntry<string> file;

		internal static ConfigEntry<float> dropAt;

		internal static ConfigEntry<float> loopFrom;

		internal static ConfigEntry<float> loopTo;

		internal static ConfigEntry<float> landingSeconds;

		internal static ConfigEntry<float> volume;

		internal static AudioClip song;

		public override string Name => "DropshipMusic";

		public override string Description => "The item dropship plays the Coffin Dance, with the drop hitting right as it lands.";

		protected override void BindConfig(ConfigFile config)
		{
			file = config.Bind<string>(Name, "File", "coffin-dance.ogg", "Song in plugins/SouthpawSystems/sounds/dropship/ (.ogg, .mp3 or .wav).");
			dropAt = config.Bind<float>(Name, "DropAt", 19.21f, "Seconds into the song that should line up with the dropship touching down.");
			loopFrom = config.Bind<float>(Name, "LoopFrom", 19.21f, "While waiting to be opened, jump back to here...");
			loopTo = config.Bind<float>(Name, "LoopTo", 49.69f, "...when the song reaches here. Pick two points a whole number of bars apart.");
			landingSeconds = config.Bind<float>(Name, "LandingSeconds", 9f, "How long the dropship takes to come down. Measured and updated automatically.");
			volume = config.Bind<float>(Name, "Volume", 1f, "Song volume, 0 to 1.");
		}

		protected override void OnEnabled(Harmony harmony)
		{
			AudioFiles.LoadAsync(Path.Combine(Plugin.Folder, "sounds", "dropship", file.Value), delegate(AudioClip clip)
			{
				song = clip;
				Plugin.Log.LogInfo((object)$"DropshipMusic: loaded '{((Object)clip).name}' ({clip.length:0.00}s).");
				if (loopTo.Value > song.length || loopFrom.Value >= loopTo.Value)
				{
					Plugin.Log.LogWarning((object)$"DropshipMusic: loop {loopFrom.Value}-{loopTo.Value}s doesn't fit the {song.length:0.00}s song; it will just play once.");
				}
			});
		}

		[HarmonyPatch(typeof(ItemDropship), "Start")]
		[HarmonyPostfix]
		private static void Start(ItemDropship __instance)
		{
			if ((Object)(object)song != (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<DropshipSong>();
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ItemDropship), "LandShipClientRpc")]
		[HarmonyPatch(typeof(ItemDropship), "DeliverVehicleClientRpc")]
		private static void Descending(ItemDropship __instance)
		{
			((Component)__instance).GetComponent<DropshipSong>()?.Begin();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ItemDropship), "ShipLandedAnimationEvent")]
		[HarmonyPatch(typeof(ItemDropship), "ShipLandedInAnimation")]
		private static void Landed(ItemDropship __instance)
		{
			((Component)__instance).GetComponent<DropshipSong>()?.Landed();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ItemDropship), "ShipLeave")]
		[HarmonyPatch(typeof(ItemDropship), "FinishDeliveringVehicleClientRpc")]
		private static void Left(ItemDropship __instance)
		{
			((Component)__instance).GetComponent<DropshipSong>()?.Stop();
		}
	}
	internal sealed class DropshipSong : MonoBehaviour
	{
		private AudioSource source;

		private AudioSource[] vanillaMusic = (AudioSource[])(object)new AudioSource[0];

		private const float FadeSeconds = 2.5f;

		private float startedAt;

		private float baseVolume;

		private bool landed;

		private bool fading;

		private void Awake()
		{
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Invalid comparison between Unknown and I4
			AudioSource val = null;
			List<AudioSource> list = new List<AudioSource>();
			AudioSource[] componentsInChildren = ((Component)this).GetComponentsInChildren<AudioSource>(true);
			foreach (AudioSource val2 in componentsInChildren)
			{
				bool flag = ((Object)val2).name.IndexOf("Music", StringComparison.OrdinalIgnoreCase) >= 0;
				ManualLogSource log = Plugin.Log;
				object[] obj = new object[4]
				{
					((Object)val2).name,
					null,
					null,
					null
				};
				AudioClip clip = val2.clip;
				obj[1] = ((clip != null) ? ((Object)clip).name : null);
				AudioClip clip2 = val2.clip;
				obj[2] = ((clip2 != null) ? clip2.length : 0f);
				obj[3] = (flag ? " <- music" : "");
				log.LogDebug((object)string.Format("DropshipMusic: dropship audio '{0}' clip '{1}' {2:0.0}s{3}", obj));
				if (flag)
				{
					list.Add(val2);
					if (val == null)
					{
						val = val2;
					}
				}
			}
			vanillaMusic = list.ToArray();
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"DropshipMusic: couldn't find the vanilla dropship music; playing ours with default settings.");
			}
			GameObject val3 = new GameObject("SouthpawSystems.DropshipSong");
			val3.transform.SetParent(((Object)(object)val != (Object)null) ? ((Component)val).transform.parent : ((Component)this).transform, false);
			if ((Object)(object)val != (Object)null)
			{
				val3.transform.localPosition = ((Component)val).transform.localPosition;
			}
			source = val3.AddComponent<AudioSource>();
			source.playOnAwake = false;
			source.loop = false;
			source.clip = DropshipMusic.song;
			source.dopplerLevel = 0f;
			if ((Object)(object)val != (Object)null)
			{
				source.outputAudioMixerGroup = val.outputAudioMixerGroup;
				source.spatialBlend = val.spatialBlend;
				source.rolloffMode = val.rolloffMode;
				source.minDistance = val.minDistance;
				source.maxDistance = val.maxDistance;
				source.spread = val.spread;
				source.priority = val.priority;
				if ((int)val.rolloffMode == 2)
				{
					source.SetCustomCurve((AudioSourceCurveType)0, val.GetCustomCurve((AudioSourceCurveType)0));
				}
				source.volume = DropshipMusic.volume.Value;
			}
			else
			{
				source.spatialBlend = 1f;
				source.maxDistance = 80f;
				source.volume = DropshipMusic.volume.Value;
			}
			baseVolume = source.volume;
		}

		public void Begin()
		{
			if (!source.isPlaying || fading)
			{
				fading = false;
				source.volume = baseVolume;
				source.time = Mathf.Clamp(DropshipMusic.dropAt.Value - DropshipMusic.landingSeconds.Value, 0f, source.clip.length - 0.1f);
				source.Play();
				startedAt = Time.time;
				landed = false;
			}
		}

		public void Landed()
		{
			if (source.isPlaying && !landed)
			{
				landed = true;
				float num = Time.time - startedAt;
				if (num > 1f && num < 60f && Math.Abs(num - DropshipMusic.landingSeconds.Value) > 0.05f)
				{
					Plugin.Log.LogInfo((object)$"DropshipMusic: landing took {num:0.00}s; timing future drops to that.");
					DropshipMusic.landingSeconds.Value = (float)Math.Round(num, 2);
				}
				if (Math.Abs(source.time - DropshipMusic.dropAt.Value) > 0.08f)
				{
					source.time = DropshipMusic.dropAt.Value;
				}
			}
		}

		public void Stop()
		{
			fading = source.isPlaying;
		}

		private void Update()
		{
			AudioSource[] array = vanillaMusic;
			foreach (AudioSource val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					val.mute = true;
				}
			}
			if (!source.isPlaying)
			{
				return;
			}
			if (fading)
			{
				AudioSource obj = source;
				obj.volume -= baseVolume * Time.deltaTime / 2.5f;
				if (source.volume <= 0f)
				{
					source.Stop();
					source.volume = baseVolume;
					fading = false;
				}
			}
			else
			{
				float value = DropshipMusic.loopFrom.Value;
				float value2 = DropshipMusic.loopTo.Value;
				if (value2 <= source.clip.length && value < value2 && source.time >= value2)
				{
					AudioSource obj2 = source;
					obj2.timeSamples -= (int)((value2 - value) * (float)source.clip.frequency);
				}
			}
		}
	}
	internal sealed class EnemyBlacklist : Feature
	{
		private static readonly Dictionary<string, string[]> Aliases = new Dictionary<string, string[]>
		{
			["backwatergunkfish"] = new string[1] { "StingrayAI" },
			["gunkfish"] = new string[1] { "StingrayAI" },
			["feiopar"] = new string[1] { "PumaAI" },
			["cadaver"] = new string[2] { "CadaverGrowthAI", "CadaverBloomAI" },
			["cadavergrowths"] = new string[2] { "CadaverGrowthAI", "CadaverBloomAI" },
			["cadaverbloom"] = new string[1] { "CadaverBloomAI" }
		};

		private static HashSet<string> banned = new HashSet<string>();

		public override string Name => "EnemyBlacklist";

		public override string Description => "Monsters that never spawn. Only the host's settings matter.";

		protected override void BindConfig(ConfigFile config)
		{
			banned = new HashSet<string>((from s in config.Bind<string>(Name, "Enemies", "Backwater Gunkfish, Cadaver, Feiopar", "Comma-separated monster names. Empty lets everything spawn. Other examples: Thumper, Bracken, Coil-Head, Jester, Nutcracker, Barber, Maneater, Old Bird, Kidnapper Fox.").Value.Split(',').Select(Normalize)
				where s.Length > 0
				select s).SelectMany((string s) => (!Aliases.TryGetValue(s, out var value)) ? new string[1] { s } : value.Select(Normalize).Append(s)));
		}

		private static string Normalize(string s)
		{
			return new string(s.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
		}

		private static bool IsBanned(EnemyType type)
		{
			if (banned.Contains(Normalize(type.enemyName)))
			{
				return true;
			}
			EnemyAI val = (((Object)(object)type.enemyPrefab != (Object)null) ? type.enemyPrefab.GetComponentInChildren<EnemyAI>() : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			string text = Normalize(((object)val).GetType().Name);
			if (!banned.Contains(text))
			{
				if (text.EndsWith("ai"))
				{
					return banned.Contains(text.Substring(0, text.Length - 2));
				}
				return false;
			}
			return true;
		}

		[HarmonyPatch(typeof(RoundManager), "LoadNewLevel")]
		[HarmonyPrefix]
		private static void LoadNewLevel(RoundManager __instance)
		{
			if (banned.Count == 0)
			{
				return;
			}
			HashSet<EnemyType> types = new HashSet<EnemyType>();
			SelectableLevel[] levels = StartOfRound.Instance.levels;
			foreach (SelectableLevel val in levels)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Add(val.Enemies);
					Add(val.OutsideEnemies);
					Add(val.DaytimeEnemies);
				}
			}
			Add(__instance.WeedEnemies);
			foreach (EnemyType item in types.ToList())
			{
				CadaverGrowthAI val2 = (((Object)(object)item.enemyPrefab != (Object)null) ? item.enemyPrefab.GetComponentInChildren<CadaverGrowthAI>() : null);
				if ((Object)(object)val2?.bloomEnemyType != (Object)null)
				{
					types.Add(val2.bloomEnemyType);
				}
			}
			List<string> list = new List<string>();
			foreach (EnemyType item2 in types)
			{
				if (!item2.spawningDisabled && IsBanned(item2))
				{
					item2.spawningDisabled = true;
					list.Add(item2.enemyName);
				}
			}
			if (list.Count > 0)
			{
				Plugin.Log.LogInfo((object)("EnemyBlacklist: disabled " + string.Join(", ", list) + "."));
			}
			void Add(IEnumerable<SpawnableEnemyWithRarity> enumerable)
			{
				if (enumerable == null)
				{
					return;
				}
				foreach (SpawnableEnemyWithRarity item3 in enumerable)
				{
					if ((Object)(object)item3?.enemyType != (Object)null)
					{
						types.Add(item3.enemyType);
					}
				}
			}
		}
	}
	internal sealed class FlashlightKey : Feature
	{
		private static InputAction key;

		public override string Name => "FlashlightKey";

		public override string Description => "Press a key to turn a flashlight in your inventory on or off without holding it.";

		protected override void BindConfig(ConfigFile config)
		{
			key = LocalInput.BindKey(config, Name, "F", "Key that toggles your flashlight.");
		}

		[HarmonyPatch(typeof(PlayerControllerB), "Update")]
		[HarmonyPostfix]
		private static void Update(PlayerControllerB __instance)
		{
			if (!key.WasPressedThisFrame() || !LocalInput.CanUseHotkeys(__instance))
			{
				return;
			}
			GrabbableObject currentlyHeldObjectServer = __instance.currentlyHeldObjectServer;
			FlashlightItem val = (FlashlightItem)(object)((currentlyHeldObjectServer is FlashlightItem) ? currentlyHeldObjectServer : null);
			if (val != null)
			{
				((GrabbableObject)val).UseItemOnClient(true);
				return;
			}
			FlashlightItem val2 = FindFlashlight(__instance);
			if (!((Object)(object)val2 == (Object)null))
			{
				((GrabbableObject)val2).UseItemOnClient(true);
				bool isBeingUsed = ((GrabbableObject)val2).isBeingUsed;
				((Behaviour)val2.flashlightBulb).enabled = false;
				((Behaviour)val2.flashlightBulbGlow).enabled = false;
				val2.usingPlayerHelmetLight = isBeingUsed;
				__instance.ChangeHelmetLight(val2.flashlightTypeID, isBeingUsed);
				if (isBeingUsed)
				{
					__instance.pocketedFlashlight = (GrabbableObject)(object)val2;
				}
				val2.PocketFlashlightServerRpc(isBeingUsed);
			}
		}

		private static FlashlightItem FindFlashlight(PlayerControllerB player)
		{
			GrabbableObject pocketedFlashlight = player.pocketedFlashlight;
			FlashlightItem val = (FlashlightItem)(object)((pocketedFlashlight is FlashlightItem) ? pocketedFlashlight : null);
			if (val != null && ((GrabbableObject)val).isHeld && (Object)(object)((GrabbableObject)val).playerHeldBy == (Object)(object)player)
			{
				return val;
			}
			FlashlightItem val2 = null;
			GrabbableObject[] itemSlots = player.ItemSlots;
			foreach (GrabbableObject val3 in itemSlots)
			{
				val2 = Better(val2, (FlashlightItem)(object)((val3 is FlashlightItem) ? val3 : null));
			}
			FlashlightItem a = val2;
			GrabbableObject itemOnlySlot = player.ItemOnlySlot;
			return Better(a, (FlashlightItem)(object)((itemOnlySlot is FlashlightItem) ? itemOnlySlot : null));
		}

		private static FlashlightItem Better(FlashlightItem a, FlashlightItem b)
		{
			if ((Object)(object)b == (Object)null)
			{
				return a;
			}
			if ((Object)(object)a == (Object)null)
			{
				return b;
			}
			if (((GrabbableObject)a).isBeingUsed != ((GrabbableObject)b).isBeingUsed)
			{
				if (!((GrabbableObject)a).isBeingUsed)
				{
					return b;
				}
				return a;
			}
			if (!(Charge((GrabbableObject)(object)b) > Charge((GrabbableObject)(object)a)))
			{
				return a;
			}
			return b;
		}

		private static float Charge(GrabbableObject item)
		{
			return item.insertedBattery?.charge ?? 0f;
		}

		[HarmonyPatch(typeof(PlayerControllerB), "KillPlayer")]
		[HarmonyPostfix]
		private static void Died(PlayerControllerB __instance)
		{
			__instance.pocketedFlashlight = null;
		}
	}
	internal sealed class GhostCam : Feature
	{
		private static InputAction key;

		private static InputAction lightKey;

		private static ConfigEntry<float> speed;

		private static ConfigEntry<float> lightRange;

		private static ConfigEntry<float> lightBrightness;

		private static bool ghost;

		private static GameObject ghostLight;

		public override string Name => "GhostCam";

		public override string Description => "When dead, press a key to fly around freely as a ghost camera. Click to go back to following a player.";

		protected override void BindConfig(ConfigFile config)
		{
			key = LocalInput.BindKey(config, Name, "G", "Toggles ghost camera while dead.");
			speed = config.Bind<float>(Name, "Speed", 8f, "Flying speed in meters per second. Hold sprint for 3x.");
			lightKey = LocalInput.BindKey(config, Name, "F", "Toggles a light while dead, ghost camera or not. Only you can see it.", "LightKey");
			lightRange = config.Bind<float>(Name, "LightRange", 50f, "How far the light reaches, in meters.");
			lightBrightness = config.Bind<float>(Name, "LightBrightness", 8f, "How bright the light is, as a multiple of the game's own dark-area night vision.");
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SetWhoToSpectate")]
		[HarmonyPrefix]
		private static bool Spectate(PlayerControllerB __instance)
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if (instance.allPlayersDead || __instance.isInGameOverAnimation > 0f)
			{
				ghost = false;
			}
			else if (key.WasPressedThisFrame() && !__instance.isTypingChat && !__instance.quickMenuManager.isMenuOpen)
			{
				ToggleGhost(__instance, instance);
			}
			if (lightKey.WasPressedThisFrame() && !__instance.isTypingChat && !__instance.quickMenuManager.isMenuOpen)
			{
				ToggleLight(__instance, instance);
			}
			if (!ghost)
			{
				return true;
			}
			Transform spectateCameraPivot = __instance.spectateCameraPivot;
			Vector3 val = Vector3.ProjectOnPlane(spectateCameraPivot.forward, Vector3.up);
			Vector3 normalized = ((Vector3)(ref val)).normalized;
			val = Vector3.ProjectOnPlane(spectateCameraPivot.right, Vector3.up);
			Vector3 normalized2 = ((Vector3)(ref val)).normalized;
			Vector2 val2 = InputSystem.actions.FindAction("Move", false).ReadValue<Vector2>();
			float num = (InputSystem.actions.FindAction("Jump", false).IsPressed() ? 1f : 0f) - (InputSystem.actions.FindAction("Crouch", false).IsPressed() ? 1f : 0f);
			float num2 = ((InputSystem.actions.FindAction("Sprint", false).ReadValue<float>() > 0.5f) ? 3f : 1f);
			if (!__instance.isTypingChat && !__instance.quickMenuManager.isMenuOpen)
			{
				spectateCameraPivot.position += (normalized * val2.y + normalized2 * val2.x + Vector3.up * num) * (speed.Value * num2 * Time.deltaTime);
			}
			((Component)instance.spectateCamera).transform.SetPositionAndRotation(spectateCameraPivot.position, spectateCameraPivot.rotation);
			return false;
		}

		private static void ToggleGhost(PlayerControllerB player, StartOfRound round)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			ghost = !ghost;
			if (ghost)
			{
				player.spectateCameraPivot.position = ((Component)round.spectateCamera).transform.position;
				((TMP_Text)HUDManager.Instance.spectatingPlayerText).text = "(Ghost: WASD to fly, jump/crouch for up/down, F for light)";
			}
			else if ((Object)(object)player.spectatedPlayerScript != (Object)null)
			{
				((TMP_Text)HUDManager.Instance.spectatingPlayerText).text = "(Spectating: " + player.spectatedPlayerScript.playerUsername + ")";
			}
		}

		private static void ToggleLight(PlayerControllerB player, StartOfRound round)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ghostLight == (Object)null)
			{
				if ((Object)(object)player.nightVision == (Object)null)
				{
					Plugin.Log.LogWarning((object)"GhostCam: no night-vision light to copy; ghost light unavailable.");
					return;
				}
				ghostLight = Object.Instantiate<GameObject>(((Component)player.nightVision).gameObject, ((Component)round.spectateCamera).transform, false);
				((Object)ghostLight).name = "SouthpawSystems.GhostLight";
				ghostLight.transform.localPosition = Vector3.zero;
				((Behaviour)ghostLight.GetComponent<Light>()).enabled = true;
				HDAdditionalLightData component = ghostLight.GetComponent<HDAdditionalLightData>();
				if ((Object)(object)component != (Object)null)
				{
					component.intensity *= lightBrightness.Value;
					component.range = lightRange.Value;
					component.affectsVolumetric = false;
				}
				ghostLight.SetActive(false);
				Light component2 = ghostLight.GetComponent<Light>();
				Plugin.Log.LogInfo((object)("GhostCam: light copied from '" + ((Object)player.nightVision).name + "' " + $"({ghostLight.GetComponents<Component>().Length} components, type {component2.type}, intensity {component2.intensity}, range {component2.range}, " + $"HD intensity {((component != null) ? new float?(component.intensity) : ((float?)null))}, HD range {((component != null) ? new float?(component.range) : ((float?)null))}), on '{((Object)round.spectateCamera).name}' (camera enabled: {((Behaviour)round.spectateCamera).enabled})."));
			}
			ghostLight.SetActive(!ghostLight.activeSelf);
			Plugin.Log.LogInfo((object)string.Format("GhostCam: light {0} at {1}.", ghostLight.activeSelf ? "on" : "off", ghostLight.transform.position));
			HUDManager.Instance.DisplayTip("Ghost light", ghostLight.activeSelf ? "On" : "Off", false, false, "LC_Tip1");
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SpectateNextPlayer")]
		[HarmonyPostfix]
		private static void SpectateNextPlayer()
		{
			ghost = false;
		}

		[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
		[HarmonyPostfix]
		private static void Revived()
		{
			ghost = false;
			if ((Object)(object)ghostLight != (Object)null)
			{
				ghostLight.SetActive(false);
			}
		}
	}
	internal sealed class HealthDisplay : Feature
	{
		private static TextMeshProUGUI text;

		private static int shown = -1;

		public override string Name => "HealthDisplay";

		public override string Description => "Shows your health as a number next to the push-to-talk icon.";

		[HarmonyPatch(typeof(HUDManager), "Start")]
		[HarmonyPostfix]
		private static void Create(HUDManager __instance)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			text = new GameObject("SouthpawSystems.HealthDisplay", new Type[1] { typeof(RectTransform) }).AddComponent<TextMeshProUGUI>();
			((Transform)((TMP_Text)text).rectTransform).SetParent(((Component)__instance.PTTIcon).transform, false);
			((TMP_Text)text).rectTransform.anchoredPosition = new Vector2(8f, -57f);
			((TMP_Text)text).font = ((TMP_Text)__instance.controlTipLines[0]).font;
			((TMP_Text)text).fontSize = 16f;
			((TMP_Text)text).overflowMode = (TextOverflowModes)0;
			shown = -1;
		}

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void Refresh()
		{
			//IL_006b: 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_007d: Unknown result type (might be due to invalid IL or missing references)
			PlayerControllerB localPlayer = LocalInput.LocalPlayer;
			if (!((Object)(object)text == (Object)null) && !((Object)(object)localPlayer == (Object)null))
			{
				int num = ((!localPlayer.isPlayerDead) ? Mathf.Max(localPlayer.health, 0) : 0);
				if (num != shown)
				{
					shown = num;
					((TMP_Text)text).text = ((num > 0) ? num.ToString().PadLeft(3) : "");
					((Graphic)text).color = Color.Lerp(Color.red, Color.green, (float)num / 100f);
				}
			}
		}
	}
	internal sealed class HideChat : Feature
	{
		private static float seconds;

		public override string Name => "HideChat";

		public override string Description => "Chat fades away completely a few seconds after the last message instead of staying on screen.";

		protected override void BindConfig(ConfigFile config)
		{
			seconds = config.Bind<float>(Name, "FadeAfterSeconds", 5f, "Seconds before chat fades out.").Value;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(HUDManager), "AddChatMessage")]
		[HarmonyPatch(typeof(HUDManager), "SubmitChat_performed")]
		[HarmonyPatch(typeof(HUDManager), "OpenMenu_performed")]
		private static void Fade(HUDManager __instance)
		{
			__instance.PingHUDElement(__instance.Chat, seconds, 1f, 0f);
		}
	}
	internal sealed class HoarderBugSounds : Feature
	{
		private static AudioClip[] clips = (AudioClip[])(object)new AudioClip[0];

		public override string Name => "HoarderBugSounds";

		public override string Description => "Hoarding bugs say yippee. Add your own .wav files to the pack's sounds/hoarder-bug folder.";

		protected override void OnEnabled(Harmony harmony)
		{
			clips = WavLoader.LoadFolder(Path.Combine(Plugin.Folder, "sounds", "hoarder-bug")).ToArray();
			if (clips.Length == 0)
			{
				Plugin.Log.LogWarning((object)"HoarderBugSounds: no .wav files found in sounds/hoarder-bug; bugs keep their normal sounds.");
			}
		}

		[HarmonyPatch(typeof(HoarderBugAI), "Start")]
		[HarmonyPostfix]
		private static void Start(HoarderBugAI __instance)
		{
			if (clips.Length != 0)
			{
				__instance.chitterSFX = clips;
			}
		}
	}
	internal sealed class IndoorHazardPlaceholderFix : Feature
	{
		public override string Name => "InteriorHazardFix";

		public override string Description => "Stops custom interiors like Tomb from freezing the host in green fog (LethalLevelLoader hazard bug).";

		protected override void OnEnabled(Harmony harmony)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method("LethalLevelLoader.AssetBundleLoader:NetworkRegisterDungeonContent", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogInfo((object)"LethalLevelLoader not found (or changed); interior hazard fix not needed.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(IndoorHazardPlaceholderFix), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Prefix(object extendedDungeonFlow, NetworkManager networkManager)
		{
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			if (extendedDungeonFlow == null)
			{
				return;
			}
			if ((Object)(object)networkManager == (Object)null)
			{
				networkManager = NetworkManager.Singleton;
			}
			if ((Object)(object)networkManager == (Object)null || networkManager.NetworkConfig == null)
			{
				return;
			}
			List<IndoorMapHazard> value = Traverse.Create(extendedDungeonFlow).Property("IndoorMapHazards", (object[])null).GetValue<List<IndoorMapHazard>>();
			if (value == null || value.Count == 0)
			{
				return;
			}
			Dictionary<string, GameObject> dictionary = new Dictionary<string, GameObject>();
			foreach (NetworkPrefab prefab in networkManager.NetworkConfig.Prefabs.Prefabs)
			{
				if ((Object)(object)prefab?.Prefab != (Object)null && !dictionary.ContainsKey(((Object)prefab.Prefab).name))
				{
					dictionary.Add(((Object)prefab.Prefab).name, prefab.Prefab);
				}
			}
			string name = ((Object)extendedDungeonFlow).name;
			foreach (IndoorMapHazard item in value)
			{
				GameObject val = item?.hazardType?.prefabToSpawn;
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.GetComponent<NetworkObject>() != (Object)null))
				{
					if (dictionary.TryGetValue(((Object)val).name, out var value2))
					{
						item.hazardType.prefabToSpawn = value2;
						Plugin.Log.LogInfo((object)("[" + name + "] Restored indoor hazard placeholder '" + ((Object)val).name + "' to the vanilla prefab."));
					}
					else
					{
						Plugin.Log.LogWarning((object)("[" + name + "] Indoor hazard '" + ((Object)val).name + "' has no NetworkObject and no vanilla prefab matches its name; leaving it to LethalLevelLoader."));
					}
				}
			}
		}
	}
	internal sealed class NoCompanyFines : Feature
	{
		private const int CompanyLevelId = 3;

		public override string Name => "NoCompanyFines";

		public override string Description => "Dying at the Company building doesn't cost the crew any money.";

		[HarmonyPatch(typeof(HUDManager), "ApplyPenalty")]
		[HarmonyPrefix]
		private static bool ApplyPenalty(HUDManager __instance, int playersDead)
		{
			StartOfRound instance = StartOfRound.Instance;
			if (instance == null || instance.currentLevel?.levelID != 3)
			{
				return true;
			}
			((TMP_Text)__instance.statsUIElements.penaltyAddition).text = $"{playersDead} casualties: waived\n(no fines at the Company)";
			((TMP_Text)__instance.statsUIElements.penaltyTotal).text = "DUE: $0";
			return false;
		}
	}
	internal sealed class NoIndoorFog : Feature
	{
		public override string Name => "NoIndoorFog";

		public override string Description => "Removes the random thick fog inside the facility. Fog outside is left alone.";

		[HarmonyPatch(typeof(RoundManager), "RefreshEnemiesList")]
		[HarmonyPostfix]
		private static void RolledFog(RoundManager __instance)
		{
			if ((Object)(object)__instance.indoorFog != (Object)null)
			{
				((Component)__instance.indoorFog).gameObject.SetActive(false);
			}
		}
	}
	internal sealed class QuietCats : Feature
	{
		private static ConfigEntry<bool> everywhere;

		public override string Name => "QuietCats";

		public override string Description => "NeedyCats' cats don't attract Eyeless Dogs (or other monsters) while on the ship. You still hear them.";

		protected override void BindConfig(ConfigFile config)
		{
			everywhere = config.Bind<bool>(Name, "Everywhere", false, "Also hide meows from monsters when the cat is off the ship (makes cats a free, harmless scrap item).");
		}

		protected override void OnEnabled(Harmony harmony)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method("NeedyCats.NeedyCatProp:PlayCatNoise", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogInfo((object)"NeedyCats not found (or changed); QuietCats not needed.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(QuietCats), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Prefix(GrabbableObject __instance, ref bool audible)
		{
			if (everywhere.Value || __instance.isInElevator || __instance.isInShipRoom)
			{
				audible = false;
			}
		}
	}
	internal sealed class ScrapBlacklist : Feature
	{
		private static HashSet<string> banned = new HashSet<string>();

		public override string Name => "ScrapBlacklist";

		public override string Description => "Scrap that never spawns. Only the host's settings matter.";

		protected override void BindConfig(ConfigFile config)
		{
			banned = new HashSet<string>(from s in config.Bind<string>(Name, "Items", "Ribcage, Bone, Ear, Foot, Hand, Knee, Tongue", "Comma-separated scrap names, as the scanner shows them. The default is Dine's body parts, except the Heart (rare and worth a lot).").Value.Split(',')
				select s.Trim() into s
				where s.Length > 0
				select s, StringComparer.OrdinalIgnoreCase);
		}

		[HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")]
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static void SpawnScrap(RoundManager __instance)
		{
			List<SpawnableItemWithRarity> list = __instance.currentLevel?.spawnableScrap;
			if (list != null && banned.Count != 0 && !list.All(Banned))
			{
				int num = list.RemoveAll(Banned);
				if (num > 0)
				{
					Plugin.Log.LogInfo((object)$"ScrapBlacklist: removed {num} item(s) from {__instance.currentLevel.PlanetName}'s scrap.");
				}
			}
			static bool Banned(SpawnableItemWithRarity s)
			{
				if ((Object)(object)s.spawnableItem != (Object)null)
				{
					return banned.Contains(s.spawnableItem.itemName);
				}
				return false;
			}
		}
	}
	internal sealed class SoundLimit : Feature
	{
		private static ConfigEntry<int> real;

		private static ConfigEntry<int> virtualVoices;

		public override string Name => "SoundLimit";

		public override string Description => "Raises the number of sounds that can play at once so footsteps and voices stop cutting out.";

		protected override void BindConfig(ConfigFile config)
		{
			real = config.Bind<int>(Name, "AudibleSounds", 128, "Sounds that can be heard at the same time. Vanilla: 40.");
			virtualVoices = config.Bind<int>(Name, "TrackedSounds", 1024, "Sounds the engine keeps track of; past this, sounds stop. Vanilla: 512.");
		}

		protected override void OnEnabled(Harmony harmony)
		{
			//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_0032: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			AudioConfiguration configuration = AudioSettings.GetConfiguration();
			int num = Mathf.Clamp(real.Value, 1, 4095);
			int num2 = Mathf.Clamp(virtualVoices.Value, num, 4095);
			if (configuration.numRealVoices == num && configuration.numVirtualVoices == num2)
			{
				Plugin.Log.LogInfo((object)$"SoundLimit: already {num}/{num2} voices, nothing to change.");
				return;
			}
			configuration.numRealVoices = num;
			configuration.numVirtualVoices = num2;
			AudioSettings.Reset(configuration);
		}
	}
	internal sealed class Suits : Feature
	{
		private const int ReservedSlots = 100;

		private const int PaddingType = 753;

		private static ConfigEntry<string> disabled;

		private static bool added;

		public override string Name => "Suits";

		public override string Description => "Adds every suit in the pack's suits folder to the rack.";

		protected override void BindConfig(ConfigFile config)
		{
			disabled = config.Bind<string>(Name, "Hidden", "", "Comma-separated suit names to leave off the rack, e.g. Minion,Smile.");
		}

		[HarmonyPatch(typeof(StartOfRound), "Start")]
		[HarmonyPrefix]
		private static void AddSuits(StartOfRound __instance)
		{
			if (added)
			{
				return;
			}
			added = true;
			List<UnlockableItem> unlockables = __instance.unlockablesList.unlockables;
			int count = unlockables.Count;
			UnlockableItem val = ((IEnumerable<UnlockableItem>)unlockables).FirstOrDefault((Func<UnlockableItem, bool>)((UnlockableItem u) => (Object)(object)u.suitMaterial != (Object)null && u.alreadyUnlocked));
			if (val == null)
			{
				Plugin.Log.LogError((object)"Suits: couldn't find a vanilla suit to copy.");
				return;
			}
			string text = Path.Combine(Plugin.Folder, "suits");
			HashSet<string> hashSet = new HashSet<string>(from s in disabled.Value.Split(',')
				select s.Trim() into s
				where s != ""
				select s, StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			IEnumerable<string> enumerable;
			if (!Directory.Exists(text))
			{
				enumerable = Enumerable.Empty<string>();
			}
			else
			{
				IEnumerable<string> enumerable2 = Directory.GetFiles(text, "*.png").OrderBy<string, string>((string p) => p, StringComparer.OrdinalIgnoreCase);
				enumerable = enumerable2;
			}
			foreach (string item in enumerable)
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item);
				if (!hashSet.Contains(fileNameWithoutExtension))
				{
					if (list.Count == 100)
					{
						Plugin.Log.LogWarning((object)$"Suits: more than {100} suits; skipping the rest.");
						break;
					}
					try
					{
						unlockables.Add(MakeSuit(val, fileNameWithoutExtension, item, text));
						list.Add(fileNameWithoutExtension);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Suits: couldn't load '" + fileNameWithoutExtension + "': " + ex.Message));
					}
				}
			}
			UnlockableItem val2 = Copy(val);
			val2.alreadyUnlocked = false;
			val2.unlockableType = 753;
			while (unlockables.Count < count + 100)
			{
				unlockables.Add(val2);
			}
			Plugin.Log.LogInfo((object)string.Format("Suits: added {0} ({1}).", list.Count, string.Join(", ", list)));
		}

		private static UnlockableItem MakeSuit(UnlockableItem template, string name, string png, string folder)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			UnlockableItem obj = Copy(template);
			obj.unlockableName = name;
			obj.alreadyUnlocked = true;
			obj.hasBeenMoved = false;
			obj.placedPosition = Vector3.zero;
			obj.placedRotation = Vector3.zero;
			Material val = Object.Instantiate<Material>(template.suitMaterial);
			((Object)val).name = name;
			val.mainTexture = (Texture)(object)LoadTexture(png);
			string text = Path.ChangeExtension(png, ".json");
			if (File.Exists(text))
			{
				ApplyTweaks(val, text, folder);
			}
			obj.suitMaterial = val;
			return obj;
		}

		private static UnlockableItem Copy(UnlockableItem item)
		{
			return JsonUtility.FromJson<UnlockableItem>(JsonUtility.ToJson((object)item));
		}

		private static Texture2D LoadTexture(string path)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			Texture2D val = new Texture2D(2, 2);
			if (!ImageConversion.LoadImage(val, File.ReadAllBytes(path)))
			{
				throw new InvalidDataException("not an image: " + path);
			}
			val.Apply(true, true);
			return val;
		}

		private static void ApplyTweaks(Material material, string json, string folder)
		{
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			string[] array = File.ReadAllLines(json);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Trim().TrimEnd(',').Split(new char[1] { ':' }, 2);
				if (array2.Length != 2)
				{
					continue;
				}
				string text = array2[0].Trim(' ', '"');
				string text2 = array2[1].Trim(' ', '"');
				if (!(text == "") && !text.StartsWith("{") && !text.StartsWith("}"))
				{
					if (text2 == "KEYWORD")
					{
						material.EnableKeyword(text);
						continue;
					}
					if (text2 == "DISABLEKEYWORD")
					{
						material.DisableKeyword(text);
						continue;
					}
					if (text2.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
					{
						material.SetTexture(text, (Texture)(object)LoadTexture(Path.Combine(folder, text2)));
						continue;
					}
					if (TryFloats(text2, out var result) && result.Length == 1)
					{
						material.SetFloat(text, result[0]);
						continue;
					}
					if (TryFloats(text2, out result) && result.Length == 4)
					{
						material.SetVector(text, new Vector4(result[0], result[1], result[2], result[3]));
						continue;
					}
					Plugin.Log.LogWarning((object)("Suits: " + Path.GetFileName(json) + ": don't know what to do with " + text + " = " + text2));
				}
			}
		}

		private static bool TryFloats(string value, out float[] result)
		{
			string[] array = value.Split(',');
			result = new float[array.Length];
			for (int i = 0; i < array.Length; i++)
			{
				if (!float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result[i]))
				{
					return false;
				}
			}
			return true;
		}

		[HarmonyPatch(typeof(StartOfRound), "PositionSuitsOnRack")]
		[HarmonyPrefix]
		private static bool PositionSuitsOnRack(StartOfRound __instance)
		{
			//IL_0072: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			UnlockableSuit[] array = (from s in Object.FindObjectsOfType<UnlockableSuit>()
				orderby s.syncedSuitID.Value
				select s).ToArray();
			float num = ((array.Length > 13) ? (2.16f / (float)Mathf.Min(array.Length, 20)) : 0.18f);
			for (int num2 = 0; num2 < array.Length; num2++)
			{
				AutoParentToShip component = ((Component)array[num2]).GetComponent<AutoParentToShip>();
				component.overrideOffset = true;
				component.positionOffset = new Vector3(-2.45f, 2.75f, -8.41f) + __instance.rightmostSuitPosition.forward * num * (float)num2;
				component.rotationOffset = new Vector3(0f, 90f, 0f);
			}
			return false;
		}
	}
	internal sealed class ToiletPaperFix : Feature
	{
		public override string Name => "ToiletPaperFix";

		public override string Description => "Fixes toilet paper floating in front of your face when held.";

		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		[HarmonyPostfix]
		private static void Awake(StartOfRound __instance)
		{
			//IL_0046: 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)
			foreach (Item items in __instance.allItemsList.itemsList)
			{
				if ((Object)(object)items != (Object)null && items.itemName == "Toilet paper")
				{
					items.positionOffset = new Vector3(0.1f, 0f, 0.2f);
				}
			}
		}
	}
	internal sealed class UtilitySlotPosition : Feature
	{
		internal enum Side
		{
			RightOfHotbar,
			LeftOfHotbar
		}

		private static ConfigEntry<Side> side;

		public override string Name => "UtilitySlotPosition";

		public override string Description => "Moves the utility slot (flashlight/walkie slot) next to your hotbar instead of the top-left corner.";

		protected override void BindConfig(ConfigFile config)
		{
			side = config.Bind<Side>(Name, "Side", Side.RightOfHotbar, "Which end of the hotbar the utility slot goes on.");
		}

		[HarmonyPatch(typeof(HUDManager), "Start")]
		[HarmonyPostfix]
		private static void Start(HUDManager __instance)
		{
			//IL_0044: 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_004f: 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_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: 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_007a: 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_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: 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_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			Image[] itemSlotIconFrames = __instance.itemSlotIconFrames;
			Image itemOnlySlotIconFrame = __instance.itemOnlySlotIconFrame;
			Transform val = ((itemOnlySlotIconFrame != null) ? ((Component)itemOnlySlotIconFrame).transform : null);
			if (!((Object)(object)val == (Object)null) && itemSlotIconFrames != null && itemSlotIconFrames.Length >= 2)
			{
				Transform transform = ((Component)itemSlotIconFrames[0]).transform;
				Transform transform2 = ((Component)itemSlotIconFrames[^1]).transform;
				Vector3 val2 = (transform2.position - transform.position) / (float)(itemSlotIconFrames.Length - 1);
				Vector3 offset = ((side.Value == Side.RightOfHotbar) ? (transform2.position + val2 * 1.3f) : (transform.position - val2 * 1.3f)) - val.position;
				Move(val, offset, null);
				Image itemOnlySlotIcon = __instance.itemOnlySlotIcon;
				Move((itemOnlySlotIcon != null) ? ((Component)itemOnlySlotIcon).transform : null, offset, val);
				TextMeshProUGUI utilitySlotKeyText = __instance.utilitySlotKeyText;
				Move((utilitySlotKeyText != null) ? ((TMP_Text)utilitySlotKeyText).transform : null, offset, val);
			}
		}

		private static void Move(Transform t, Vector3 offset, Transform movedParent)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)t == (Object)null) && (!((Object)(object)movedParent != (Object)null) || !t.IsChildOf(movedParent)))
			{
				t.position += offset;
			}
		}
	}
	internal sealed class WalkieKey : Feature
	{
		private static InputAction key;

		private static WalkieTalkie talking;

		public override string Name => "WalkieKey";

		public override string Description => "Hold a key to talk into a switched-on walkie-talkie in your inventory without holding it.";

		protected override void BindConfig(ConfigFile config)
		{
			key = LocalInput.BindKey(config, Name, "R", "Hold to talk into your walkie-talkie.");
		}

		[HarmonyPatch(typeof(PlayerControllerB), "Update")]
		[HarmonyPostfix]
		private static void Update(PlayerControllerB __instance)
		{
			if ((Object)(object)__instance != (Object)(object)LocalInput.LocalPlayer)
			{
				return;
			}
			if ((Object)(object)talking != (Object)null && !key.IsPressed())
			{
				if (((GrabbableObject)talking).isHeld && (Object)(object)((GrabbableObject)talking).playerHeldBy == (Object)(object)__instance)
				{
					((GrabbableObject)talking).UseItemOnClient(false);
				}
				talking = null;
			}
			else if ((Object)(object)talking == (Object)null && key.WasPressedThisFrame() && LocalInput.CanUseHotkeys(__instance))
			{
				talking = FindWalkie(__instance);
				WalkieTalkie obj = talking;
				if (obj != null)
				{
					((GrabbableObject)obj).UseItemOnClient(true);
				}
			}
		}

		private static WalkieTalkie FindWalkie(PlayerControllerB player)
		{
			GrabbableObject currentlyHeldObjectServer = player.currentlyHeldObjectServer;
			WalkieTalkie val = (WalkieTalkie)(object)((currentlyHeldObjectServer is WalkieTalkie) ? currentlyHeldObjectServer : null);
			if (val != null && ((GrabbableObject)val).isBeingUsed)
			{
				return val;
			}
			GrabbableObject[] itemSlots = player.ItemSlots;
			foreach (GrabbableObject obj in itemSlots)
			{
				WalkieTalkie val2 = (WalkieTalkie)(object)((obj is WalkieTalkie) ? obj : null);
				if (val2 != null && ((GrabbableObject)val2).isBeingUsed)
				{
					return val2;
				}
			}
			GrabbableObject itemOnlySlot = player.ItemOnlySlot;
			WalkieTalkie val3 = (WalkieTalkie)(object)((itemOnlySlot is WalkieTalkie) ? itemOnlySlot : null);
			if (val3 == null || !((GrabbableObject)val3).isBeingUsed)
			{
				return null;
			}
			return val3;
		}
	}
}
namespace SouthpawSystems.Core
{
	internal static class AudioFiles
	{
		private sealed class Runner : MonoBehaviour
		{
		}

		public static void LoadAsync(string path, Action<AudioClip> onLoaded)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			GameObject val = new GameObject("SouthpawSystems.AudioLoader");
			Object.DontDestroyOnLoad((Object)(object)val);
			((Object)val).hideFlags = (HideFlags)61;
			((MonoBehaviour)val.AddComponent<Runner>()).StartCoroutine(Load(path, onLoaded, val));
		}

		private static IEnumerator Load(string path, Action<AudioClip> onLoaded, GameObject runner)
		{
			try
			{
				string text = Path.GetExtension(path).ToLowerInvariant();
				AudioType val;
				switch (text)
				{
				case ".wav":
					onLoaded(WavLoader.Load(path));
					yield break;
				case ".ogg":
					val = (AudioType)14;
					break;
				case ".mp3":
					val = (AudioType)13;
					break;
				default:
					val = (AudioType)0;
					break;
				}
				AudioType val2 = val;
				if ((int)val2 == 0)
				{
					Plugin.Log.LogError((object)("Couldn't load '" + path + "': unsupported sound file type '" + text + "'."));
					yield break;
				}
				UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, val2);
				try
				{
					((DownloadHandlerAudioClip)request.downloadHandler).streamAudio = false;
					yield return request.SendWebRequest();
					if ((int)request.result != 1)
					{
						Plugin.Log.LogError((object)("Couldn't load '" + path + "': " + request.error));
						yield break;
					}
					AudioClip content = DownloadHandlerAudioClip.GetContent(request);
					((Object)content).name = Path.GetFileNameWithoutExtension(path);
					onLoaded(content);
				}
				finally
				{
					((IDisposable)request)?.Dispose();
				}
			}
			finally
			{
				Object.Destroy((Object)(object)runner);
			}
		}
	}
	internal abstract class Feature
	{
		public abstract string Name { get; }

		public abstract string Description { get; }

		protected virtual bool EnabledByDefault => true;

		public bool IsEnabled { get; private set; }

		internal void Load(ConfigFile config)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			IsEnabled = config.Bind<bool>(Name, "Enabled", EnabledByDefault, Description + "\nChanging this needs a game restart.").Value;
			BindConfig(config);
			if (!IsEnabled)
			{
				return;
			}
			Harmony val = new Harmony("kirk.southpawsystems." + Name);
			try
			{
				if (HasAttributePatches(GetType()))
				{
					val.PatchAll(GetType());
				}
				OnEnabled(val);
			}
			catch
			{
				IsEnabled = false;
				val.UnpatchSelf();
				throw;
			}
		}

		private static bool HasAttributePatches(Type type)
		{
			if (!type.IsDefined(typeof(HarmonyPatch), inherit: false))
			{
				return AccessTools.GetDeclaredMethods(type).Any((MethodInfo m) => m.IsDefined(typeof(HarmonyPatch), inherit: false));
			}
			return true;
		}

		protected virtual void BindConfig(ConfigFile config)
		{
		}

		protected virtual void OnEnabled(Harmony harmony)
		{
		}
	}
	internal static class LocalInput
	{
		public static PlayerControllerB LocalPlayer
		{
			get
			{
				if (!((Object)(object)GameNetworkManager.Instance != (Object)null))
				{
					return null;
				}
				return GameNetworkManager.Instance.localPlayerController;
			}
		}

		public static InputAction BindKey(ConfigFile config, string section, string defaultKey, string description, string setting = "Key")
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			ConfigEntry<string> entry = config.Bind<string>(section, setting, defaultKey, description + "\nA key name like F or R, or a Unity input path like <Mouse>/forwardButton.");
			InputAction action = new InputAction(section + "." + setting, (InputActionType)1, ToPath(entry.Value), (string)null, (string)null, (string)null);
			action.Enable();
			entry.SettingChanged += delegate
			{
				InputActionRebindingExtensions.ApplyBindingOverride(action, ToPath(entry.Value), (string)null, (string)null);
			};
			return action;
		}

		private static string ToPath(string key)
		{
			key = key.Trim();
			if (!key.StartsWith("<"))
			{
				return "<Keyboard>/" + key.ToLowerInvariant();
			}
			return key;
		}

		public static bool CanUseHotkeys(PlayerControllerB player)
		{
			if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)LocalPlayer && player.isPlayerControlled && !player.isPlayerDead && !player.isTypingChat && !player.inTerminalMenu && !player.inSpecialMenu && ((Object)(object)player.quickMenuManager == (Object)null || !player.quickMenuManager.isMenuOpen) && ((Object)(object)ShipBuildModeManager.Instance == (Object)null || !ShipBuildModeManager.Instance.InBuildMode))
			{
				return Application.isFocused;
			}
			return false;
		}
	}
	internal static class WavLoader
	{
		public static List<AudioClip> LoadFolder(string folder)
		{
			List<AudioClip> list = new List<AudioClip>();
			if (!Directory.Exists(folder))
			{
				return list;
			}
			string[] files = Directory.GetFiles(folder, "*.wav");
			foreach (string text in files)
			{
				try
				{
					list.Add(Load(text));
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Couldn't load sound '" + text + "': " + ex.Message));
				}
			}
			return list;
		}

		public static AudioClip Load(string path)
		{
			using BinaryReader binaryReader = new BinaryReader(File.OpenRead(path));
			if (new string(binaryReader.ReadChars(4)) != "RIFF")
			{
				throw new InvalidDataException("not a RIFF file");
			}
			binaryReader.ReadInt32();
			if (new string(binaryReader.ReadChars(4)) != "WAVE")
			{
				throw new InvalidDataException("not a WAVE file");
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int bits = 0;
			byte[] array = null;
			while (binaryReader.BaseStream.Position + 8 <= binaryReader.BaseStream.Length)
			{
				string text = new string(binaryReader.ReadChars(4));
				int num4 = binaryReader.ReadInt32();
				long val = binaryReader.BaseStream.Position + num4 + (num4 & 1);
				if (text == "fmt ")
				{
					num = binaryReader.ReadInt16();
					num2 = binaryReader.ReadInt16();
					num3 = binaryReader.ReadInt32();
					binaryReader.ReadInt32();
					binaryReader.ReadInt16();
					bits = binaryReader.ReadInt16();
					if (num == 65534 && num4 >= 26)
					{
						binaryReader.ReadInt16();
						binaryReader.ReadInt16();
						binaryReader.ReadInt32();
						num = binaryReader.ReadInt16();
					}
				}
				else if (text == "data")
				{
					array = binaryReader.ReadBytes(num4);
				}
				binaryReader.BaseStream.Position = Math.Min(val, binaryReader.BaseStream.Length);
			}
			if (array == null || num2 == 0)
			{
				throw new InvalidDataException("missing fmt or data chunk");
			}
			float[] array2 = Decode(array, num, bits);
			AudioClip obj = AudioClip.Create(Path.GetFileNameWithoutExtension(path), array2.Length / num2, num2, num3, false);
			obj.SetData(array2, 0);
			return obj;
		}

		private static float[] Decode(byte[] data, int format, int bits)
		{
			int num = bits / 8;
			float[] array = new float[data.Length / num];
			float[] array2;
			int num3;
			float num4;
			for (int i = 0; i < array.Length; array2[num3] = num4, i++)
			{
				int num2 = i * num;
				array2 = array;
				num3 = i;
				switch (format)
				{
				case 3:
					if (bits != 32)
					{
						break;
					}
					num4 = BitConverter.ToSingle(data, num2);
					continue;
				case 1:
					switch (bits)
					{
					case 8:
						num4 = (float)(data[num2] - 128) / 128f;
						continue;
					case 16:
						num4 = (float)BitConverter.ToInt16(data, num2) / 32768f;
						continue;
					case 24:
						num4 = (float)(((data[num2] << 8) | (data[num2 + 1] << 16) | (data[num2 + 2] << 24)) >> 8) / 8388608f;
						continue;
					case 32:
						num4 = (float)BitConverter.ToInt32(data, num2) / 2.1474836E+09f;
						continue;
					}
					break;
				}
				throw new InvalidDataException($"unsupported wav format {format}, {bits}-bit");
			}
			return array;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}