Decompiled source of Bitencola Hot Fixes v1.2.1

BitencolaHotFixes.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BitencolaHotFixes.Core;
using BitencolaHotFixes.Features;
using BitencolaHotFixes.Features.Hud;
using BitencolaHotFixes.Features.Remains;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using LethalLib.Modules;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
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("BitencolaHotFixes")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+3c2756e2c376a7a2ba573d090825e802e951661a")]
[assembly: AssemblyProduct("BitencolaHotFixes")]
[assembly: AssemblyTitle("BitencolaHotFixes")]
[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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 BitencolaHotFixes
{
	[BepInPlugin("Bitencola.HotFixes", "Bitencola Hot Fixes", "1.2.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "Bitencola.HotFixes";

		public const string PluginName = "Bitencola Hot Fixes";

		public const string PluginVersion = "1.2.1";

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static PluginConfig Cfg { get; private set; }

		private void Awake()
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new PluginConfig(((BaseUnityPlugin)this).Config);
			try
			{
				RemainsItems.CreateAndRegister();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Falha ao registrar os restos de monstro: {arg}");
			}
			try
			{
				KillCreditPatches.Register();
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"Falha ao ligar quem matou às caixas de restos: {arg2}");
			}
			try
			{
				SetUpInputs();
			}
			catch (Exception arg3)
			{
				Log.LogError((object)$"Falha ao criar as teclas do mod (1-4, C e X ficam sem efeito): {arg3}");
			}
			PatchEachClass(new Harmony("Bitencola.HotFixes"));
			Log.LogInfo((object)"Bitencola Hot Fixes 1.2.1 carregado");
		}

		private static void PatchEachClass(Harmony harmony)
		{
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"Falha ao aplicar os patches de {type.Name}: {arg}");
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void SetUpInputs()
		{
			HotFixesInputs.Create();
			SlotHotkeys.Bind(HotFixesInputs.Instance);
			DropAll.Bind(HotFixesInputs.Instance);
			DropScrap.Bind(HotFixesInputs.Instance);
		}
	}
	public class PluginConfig
	{
		private const string SecTeclas = "1. Teclas";

		private const string SecCura = "2. Cura";

		private const string SecNave = "3. Nave";

		private const string SecMonstros = "4. Monstros";

		private const string SecGrupo = "5. Grupo";

		private readonly Dictionary<MonsterTier, (ConfigEntry<int> min, ConfigEntry<int> max)> _tierRanges = new Dictionary<MonsterTier, (ConfigEntry<int>, ConfigEntry<int>)>();

		public ConfigEntry<bool> SlotHotkeysEnabled { get; }

		public ConfigEntry<float> SlotSwitchCooldownSeconds { get; }

		public ConfigEntry<bool> ShowSlotNumbers { get; }

		public ConfigEntry<bool> DropAllEnabled { get; }

		public ConfigEntry<bool> SellAllAtCounterEnabled { get; }

		public ConfigEntry<bool> DropScrapEnabled { get; }

		public ConfigEntry<bool> MoveEmotesEnabled { get; }

		public ConfigEntry<bool> ShowSlotValues { get; }

		public ConfigEntry<bool> WeightInKilograms { get; }

		public ConfigEntry<bool> RegenEnabled { get; }

		public ConfigEntry<float> RegenDelaySeconds { get; }

		public ConfigEntry<float> RegenSecondsPerPoint { get; }

		public ConfigEntry<bool> ShowHealthNumbers { get; }

		public ConfigEntry<bool> ShipHealEnabled { get; }

		public ConfigEntry<bool> ShipRechargeEnabled { get; }

		public ConfigEntry<float> ShipRechargeSecondsToFull { get; }

		public ConfigEntry<bool> RemainsEnabled { get; }

		public ConfigEntry<bool> ShowKillerEnabled { get; }

		public ConfigEntry<bool> DeathNoticesEnabled { get; }

		public ConfigEntry<bool> DailyScoreboardEnabled { get; }

		public PluginConfig(ConfigFile config)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Expected O, but got Unknown
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Expected O, but got Unknown
			//IL_0333: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Expected O, but got Unknown
			SlotHotkeysEnabled = config.Bind<bool>("1. Teclas", "Trocar slot com numeros", true, "As teclas 1, 2, 3 e 4 selecionam os slots do inventário. Dá para remapear no menu de Controles do jogo.");
			SlotSwitchCooldownSeconds = config.Bind<float>("1. Teclas", "Intervalo entre trocas (s)", 0.1f, new ConfigDescription("Tempo mínimo entre duas trocas pelas teclas 1 a 4. Um aperto dentro desse tempo não se perde: a troca acontece assim que o tempo passa. A rolagem do mouse continua com o tempo do jogo (0,3 s).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f), Array.Empty<object>()));
			ShowSlotNumbers = config.Bind<bool>("1. Teclas", "Mostrar numeros nos slots", true, "Mostra [1], [2], [3] e [4] em cima dos slots, como o [TAB] do slot extra. Se a tecla for trocada no menu de Controles, o texto acompanha.");
			DropAllEnabled = config.Bind<bool>("1. Teclas", "Largar tudo", true, "Uma tecla (C por padrão) larga todos os itens de uma vez. Dá para remapear no menu de Controles do jogo.");
			SellAllAtCounterEnabled = config.Bind<bool>("1. Teclas", "Vender tudo no balcao", true, "No balcão da Companhia, a tecla de largar tudo (C) põe toda a sucata no balcão, um item por vez, em vez de largar tudo no chão. As ferramentas ficam com você.");
			DropScrapEnabled = config.Bind<bool>("1. Teclas", "Largar so sucata", true, "Uma tecla (X por padrão) larga no chão só a sucata dos slots normais, um item por vez. Ferramentas e o item do slot extra ficam com você. Dá para remapear no menu de Controles do jogo.");
			MoveEmotesEnabled = config.Bind<bool>("1. Teclas", "Mover emotes para 5 e 6", true, "Move os emotes do jogo (dançar e apontar) das teclas 1 e 2 para 5 e 6, liberando 1 a 4 para os slots.");
			ShowSlotValues = config.Bind<bool>("1. Teclas", "Mostrar valor nos slots", true, "Mostra o valor de venda de cada sucata embaixo do slot em que ela está.");
			WeightInKilograms = config.Bind<bool>("1. Teclas", "Peso em kg", true, "Mostra o peso que você carrega em quilos (kg) em vez de libras (lb).");
			RegenEnabled = config.Bind<bool>("2. Cura", "Cura gradual", true, "Recupera vida aos poucos depois de levar dano, até 100. A regeneração nativa do jogo (abaixo de 20) continua igual.");
			RegenDelaySeconds = config.Bind<float>("2. Cura", "Espera apos dano (s)", 5f, "Segundos sem levar dano antes de a cura gradual começar. Novo dano reinicia a contagem.");
			RegenSecondsPerPoint = config.Bind<float>("2. Cura", "Segundos por ponto de vida", 1.5f, "Quantos segundos leva para recuperar 1 ponto de vida durante a cura gradual.");
			ShowHealthNumbers = config.Bind<bool>("2. Cura", "Mostrar vida em numeros", true, "Mostra sua vida em números (por exemplo 75/100) perto do ícone do jogador.");
			ShipHealEnabled = config.Bind<bool>("3. Nave", "Curar ao entrar na nave", true, "Ao entrar na nave, a vida volta a 100 na hora e o estado de gravemente ferido é removido.");
			ShipRechargeEnabled = config.Bind<bool>("3. Nave", "Recarregar baterias na nave", true, "Itens com bateria que você carrega recarregam sozinhos dentro da nave. Quem hospeda a partida também recarrega os itens soltos na nave.");
			ShipRechargeSecondsToFull = config.Bind<float>("3. Nave", "Segundos para recarga completa", 20f, new ConfigDescription("Quantos segundos uma bateria vazia leva para chegar a 100% dentro da nave.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 300f), Array.Empty<object>()));
			RemainsEnabled = config.Bind<bool>("4. Monstros", "Monstros vendiveis", true, "Monstros mortos viram um item carregável que pode ser vendido na Companhia como sucata.");
			ShowKillerEnabled = config.Bind<bool>("4. Monstros", "Mostrar quem matou", true, "O scanner da caixa de restos mostra quem matou o monstro: o último jogador que acertou um golpe nele. Monstros que nenhum jogador acertou (mortos por mina, torreta ou queda) não mostram ninguém.");
			DeathNoticesEnabled = config.Bind<bool>("5. Grupo", "Avisar morte de amigos", true, "Mostra um aviso na tela com o nome e a causa da morte quando outro jogador morre.");
			DailyScoreboardEnabled = config.Bind<bool>("5. Grupo", "Placar do dia", true, "No fim do dia, quando a nave decola, mostra quantos monstros cada jogador matou e o valor dos restos.");
			AcceptableValueRange<int> val = new AcceptableValueRange<int>(1, 10000);
			foreach (MonsterTier value in Enum.GetValues(typeof(MonsterTier)))
			{
				TierSpec tierSpec = MonsterCatalog.Tiers[value];
				string text = MonsterCatalog.ConfigPrefix(value);
				ConfigEntry<int> item = config.Bind<int>("4. Monstros", text + " - valor minimo", tierSpec.MinValue, new ConfigDescription("Valor mínimo de venda dos restos de monstros da categoria \"" + text + "\".", (AcceptableValueBase)(object)val, Array.Empty<object>()));
				ConfigEntry<int> item2 = config.Bind<int>("4. Monstros", text + " - valor maximo", tierSpec.MaxValue, new ConfigDescription("Valor máximo de venda dos restos de monstros da categoria \"" + text + "\".", (AcceptableValueBase)(object)val, Array.Empty<object>()));
				_tierRanges[value] = (item, item2);
			}
		}

		public (int min, int max) GetTierRange(MonsterTier tier)
		{
			var (val, val2) = _tierRanges[tier];
			return (min: val.Value, max: val2.Value);
		}
	}
}
namespace BitencolaHotFixes.Features
{
	[HarmonyPatch(typeof(HUDManager), "Update")]
	internal static class DeathNotices
	{
		private static readonly DeathWatcher Watcher = new DeathWatcher();

		private static readonly List<PlayerLife> Lives = new List<PlayerLife>();

		private static StartOfRound? _lastRound;

		private const string TipSource = "morte";

		private static readonly List<(string name, string cause)> Recent = new List<(string, string)>();

		[HarmonyPostfix]
		private static void UpdatePostfix()
		{
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Expected I4, but got Unknown
			try
			{
				if (!Plugin.Cfg.DeathNoticesEnabled.Value)
				{
					return;
				}
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null)
				{
					return;
				}
				if (instance != _lastRound)
				{
					_lastRound = instance;
					Recent.Clear();
					Watcher.Reset();
				}
				Lives.Clear();
				PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
				foreach (PlayerControllerB val in allPlayerScripts)
				{
					if (!((Object)(object)val == (Object)null))
					{
						Lives.Add(new PlayerLife((int)val.playerClientId, val.isPlayerControlled || val.isPlayerDead, val.isPlayerDead));
					}
				}
				IReadOnlyList<int> readOnlyList = Watcher.Update(Lives);
				if (readOnlyList.Count == 0)
				{
					return;
				}
				PlayerControllerB val2 = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
				if (!TipQueue.IsQueuedOrOnScreen("morte"))
				{
					Recent.Clear();
				}
				bool flag = false;
				for (int j = 0; j < readOnlyList.Count; j++)
				{
					PlayerControllerB val3 = null;
					for (int k = 0; k < allPlayerScripts.Length; k++)
					{
						if ((Object)(object)allPlayerScripts[k] != (Object)null && (int)allPlayerScripts[k].playerClientId == readOnlyList[j])
						{
							val3 = allPlayerScripts[k];
							break;
						}
					}
					if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3 == (Object)(object)val2))
					{
						Recent.Add((val3.playerUsername, DeathCauseText.For((int)val3.causeOfDeath)));
						flag = true;
					}
				}
				if (flag)
				{
					var (header, body) = DeathNoticeText.Build(Recent);
					TipQueue.Show("morte", header, body);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("DeathNotices: " + ex));
			}
		}
	}
	internal static class DropAll
	{
		public static void Bind(HotFixesInputs inputs)
		{
			inputs.DropAll.performed += OnDropPressed;
		}

		private static void OnDropPressed(CallbackContext context)
		{
			try
			{
				if (((CallbackContext)(ref context)).performed && Plugin.Cfg.DropAllEnabled.Value && !InventoryUnloader.IsRunning && LocalPlayerInput.TryGet(out PlayerControllerB player, out PlayerInputState state) && InputGate.CanDropAll(in state))
				{
					if ((Object)(object)ShipBuildModeManager.Instance != (Object)null)
					{
						ShipBuildModeManager.Instance.CancelBuildMode(true);
					}
					if (Plugin.Cfg.SellAllAtCounterEnabled.Value && InventoryUnloader.HasScrapToUnload(player) && InventoryUnloader.TryGetCounterInReach(player, out DepositItemsDesk _))
					{
						InventoryUnloader.Start(player, UnloadMode.PlaceOnCounter);
					}
					else
					{
						player.DropAllHeldItemsAndSyncNonexact();
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao largar tudo: {arg}");
			}
		}
	}
	internal static class DropScrap
	{
		public static void Bind(HotFixesInputs inputs)
		{
			inputs.DropScrap.performed += OnDropScrapPressed;
		}

		private static void OnDropScrapPressed(CallbackContext context)
		{
			try
			{
				if (((CallbackContext)(ref context)).performed && Plugin.Cfg.DropScrapEnabled.Value && !InventoryUnloader.IsRunning && LocalPlayerInput.TryGet(out PlayerControllerB player, out PlayerInputState state) && InputGate.CanUnloadScrap(in state, InventoryUnloader.InBuildMode()))
				{
					InventoryUnloader.Start(player, UnloadMode.DropOnFloor);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao largar só a sucata: {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class EmoteRebindPatches
	{
		private static readonly string[] EmoteActions = new string[2] { "Emote1", "Emote2" };

		[HarmonyPostfix]
		[HarmonyPatch(typeof(IngamePlayerSettings), "LoadSettingsFromPrefs")]
		private static void AfterLoadSettings()
		{
			ApplyEmoteRemap();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(IngamePlayerSettings), "DiscardChangedSettings")]
		private static void AfterDiscardChanges(IngamePlayerSettings __instance)
		{
			RefreshKeybindMenuIfChanged(__instance);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(IngamePlayerSettings), "ResetAllKeybinds")]
		private static void AfterResetKeybinds(IngamePlayerSettings __instance)
		{
			RefreshKeybindMenuIfChanged(__instance);
		}

		private static void RefreshKeybindMenuIfChanged(IngamePlayerSettings settings)
		{
			if (!ApplyEmoteRemap())
			{
				return;
			}
			try
			{
				settings.UpdateAllKeybindOptions();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao atualizar o menu de Controles: {arg}");
			}
		}

		private static bool ApplyEmoteRemap()
		{
			//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_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: 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)
			try
			{
				if (!Plugin.Cfg.MoveEmotesEnabled.Value)
				{
					return false;
				}
				InputActionAsset actions = InputSystem.actions;
				if ((Object)(object)actions == (Object)null)
				{
					return false;
				}
				bool result = false;
				string[] emoteActions = EmoteActions;
				foreach (string text in emoteActions)
				{
					InputAction val = actions.FindAction(text, false);
					if (val == null)
					{
						continue;
					}
					for (int j = 0; j < val.bindings.Count; j++)
					{
						InputBinding val2 = val.bindings[j];
						string text2 = EmoteRemap.NewPath(text, ((InputBinding)(ref val2)).effectivePath);
						if (text2 != null)
						{
							InputActionRebindingExtensions.ApplyBindingOverride(val, j, text2);
							result = true;
						}
					}
				}
				return result;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mover os emotes para 5 e 6: {arg}");
				return false;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "Update")]
	internal static class HealthPatches
	{
		private const int MaxHealth = 100;

		private const int VanillaFloor = 20;

		private const float MinDelaySeconds = 0f;

		private const float MinSecondsPerPoint = 0.1f;

		private static readonly ShipEntryTracker Ship = new ShipEntryTracker();

		private static RegenTracker? _regen;

		private static float _regenDelay;

		private static float _regenSecondsPerPoint;

		[HarmonyPostfix]
		private static void UpdatePostfix(PlayerControllerB __instance)
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)__instance != (Object)(object)instance.localPlayerController)
			{
				return;
			}
			if (__instance.isPlayerDead)
			{
				_regen?.Reset();
				Ship.Update(__instance.isInHangarShipRoom, isAlive: false);
			}
			else if (__instance.isPlayerControlled)
			{
				PluginConfig cfg = Plugin.Cfg;
				if (Ship.Update(__instance.isInHangarShipRoom, isAlive: true) && cfg.ShipHealEnabled.Value)
				{
					HealToFullInShip(__instance);
				}
				if (cfg.RegenEnabled.Value)
				{
					RegenerateGradually(__instance, cfg);
				}
				else
				{
					_regen?.Reset();
				}
			}
		}

		private static void HealToFullInShip(PlayerControllerB player)
		{
			if (player.health < 100 || player.criticallyInjured)
			{
				player.health = 100;
				if (player.criticallyInjured)
				{
					player.MakeCriticallyInjured(false);
				}
				HUDManager.Instance.UpdateHealthUI(100, false);
				Plugin.Log.LogInfo((object)"Cura na nave");
			}
		}

		private static void RegenerateGradually(PlayerControllerB player, PluginConfig cfg)
		{
			int num = EnsureRegenMatchesConfig(cfg).Tick(player.health, Time.deltaTime);
			if (num > 0)
			{
				player.health += num;
				HUDManager.Instance.UpdateHealthUI(player.health, false);
				if (player.health >= 20 && player.criticallyInjured)
				{
					player.MakeCriticallyInjured(false);
				}
			}
		}

		private static RegenTracker EnsureRegenMatchesConfig(PluginConfig cfg)
		{
			float num = Mathf.Max(cfg.RegenDelaySeconds.Value, 0f);
			float num2 = Mathf.Max(cfg.RegenSecondsPerPoint.Value, 0.1f);
			if (_regen == null || num != _regenDelay || num2 != _regenSecondsPerPoint)
			{
				_regenDelay = num;
				_regenSecondsPerPoint = num2;
				_regen = new RegenTracker(num, num2);
			}
			return _regen;
		}
	}
	public class HotFixesInputs : LcInputActions
	{
		public static HotFixesInputs Instance { get; private set; }

		[InputAction("<Keyboard>/1", Name = "Slot 1")]
		public InputAction Slot1 { get; set; }

		[InputAction("<Keyboard>/2", Name = "Slot 2")]
		public InputAction Slot2 { get; set; }

		[InputAction("<Keyboard>/3", Name = "Slot 3")]
		public InputAction Slot3 { get; set; }

		[InputAction("<Keyboard>/4", Name = "Slot 4")]
		public InputAction Slot4 { get; set; }

		[InputAction("<Keyboard>/c", Name = "Largar tudo")]
		public InputAction DropAll { get; set; }

		[InputAction("<Keyboard>/x", Name = "Largar so sucata")]
		public InputAction DropScrap { get; set; }

		public static void Create()
		{
			Instance = new HotFixesInputs();
		}
	}
	internal enum UnloadMode
	{
		DropOnFloor,
		PlaceOnCounter
	}
	internal static class InventoryUnloader
	{
		private const int UtilitySlot = 50;

		private const int CounterObjectLimit = 150;

		private const float SettleAfterSwitchSeconds = 0.15f;

		private const float PauseBetweenItemsSeconds = 0.15f;

		private const float StepTimeoutSeconds = 1f;

		private const float StaleAfterSeconds = 3f;

		private static int _lastSequenceId;

		private static int _runningId;

		private static PlayerControllerB? _runningOn;

		private static float _heartbeat;

		private static DepositItemsDesk? _counter;

		private static bool _counterSearched;

		private static int _counterScenesKey;

		public static bool IsRunning
		{
			get
			{
				if (_runningId != 0 && (Object)(object)_runningOn != (Object)null)
				{
					return Time.time - _heartbeat < 3f;
				}
				return false;
			}
		}

		public static void Start(PlayerControllerB player, UnloadMode mode)
		{
			if (IsRunning || (Object)(object)player == (Object)null || !((Behaviour)player).isActiveAndEnabled)
			{
				return;
			}
			int currentItemSlot = player.currentItemSlot;
			IReadOnlyList<int> readOnlyList = ScrapSelection.UnloadOrder(ClassifySlots(player), currentItemSlot);
			if (readOnlyList.Count == 0)
			{
				return;
			}
			DepositItemsDesk desk = null;
			if (mode == UnloadMode.PlaceOnCounter && !TryGetCounterInReach(player, out desk))
			{
				return;
			}
			int id = (_runningId = ++_lastSequenceId);
			_runningOn = player;
			_heartbeat = Time.time;
			try
			{
				((MonoBehaviour)player).StartCoroutine(Guarded(id, Run(player, mode, desk, readOnlyList, currentItemSlot)));
			}
			catch
			{
				Release(id);
				throw;
			}
		}

		public static bool HasScrapToUnload(PlayerControllerB player)
		{
			return ScrapSelection.SlotsToUnload(ClassifySlots(player)).Count > 0;
		}

		public static bool TryGetCounterInReach(PlayerControllerB player, [NotNullWhen(true)] out DepositItemsDesk? desk)
		{
			desk = FindCounter();
			if ((Object)(object)desk != (Object)null)
			{
				return InReach(desk, player);
			}
			return false;
		}

		public static bool InBuildMode()
		{
			ShipBuildModeManager instance = ShipBuildModeManager.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.InBuildMode;
			}
			return false;
		}

		private static IEnumerator Guarded(int id, IEnumerator steps)
		{
			try
			{
				while (true)
				{
					object current;
					try
					{
						if (_runningId != id)
						{
							break;
						}
						_heartbeat = Time.time;
						if (!steps.MoveNext())
						{
							break;
						}
						current = steps.Current;
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"Falha ao tirar a sucata dos slots; a sequência parou: {arg}");
						break;
					}
					yield return current;
				}
			}
			finally
			{
				Release(id);
			}
		}

		private static IEnumerator Run(PlayerControllerB player, UnloadMode mode, DepositItemsDesk? desk, IReadOnlyList<int> order, int savedSlot)
		{
			bool canRestoreSlot = true;
			foreach (int slot in order)
			{
				if (!CanStartStep(player, mode, desk, out string reason))
				{
					Stopped(reason);
					break;
				}
				if (player.currentItemSlot != slot)
				{
					if (player.twoHanded)
					{
						Stopped("item de duas mãos na mão");
						break;
					}
					SlotSwitcher.SwitchNow(player, slot);
				}
				float deadline = Time.time + 1f;
				while (!SettledAfterSwitch(player) && Time.time < deadline && IsLocalPlayer(player))
				{
					yield return null;
				}
				if (mode == UnloadMode.DropOnFloor)
				{
					yield return (object)new WaitForEndOfFrame();
				}
				if (!CanActNow(player, mode, desk, out reason))
				{
					Stopped(reason);
					break;
				}
				if (HoldsScrapFrom(player, slot))
				{
					if (mode == UnloadMode.PlaceOnCounter)
					{
						desk.PlaceItemOnCounter(player);
					}
					else
					{
						player.DiscardHeldObject(false, (NetworkObject)null, default(Vector3), true);
					}
					deadline = Time.time + 1f;
					while (player.throwingObject && Time.time < deadline && IsLocalPlayer(player))
					{
						yield return null;
					}
					if (player.throwingObject)
					{
						canRestoreSlot = false;
						Plugin.Log.LogWarning((object)"O jogo não confirmou a tempo que o item foi largado; a sequência parou.");
						break;
					}
				}
				yield return (object)new WaitForSeconds(0.15f);
			}
			if (canRestoreSlot)
			{
				RestoreSlot(player, savedSlot);
			}
		}

		private static bool CanStartStep(PlayerControllerB player, UnloadMode mode, DepositItemsDesk? desk, out string reason)
		{
			if (!LocalPlayerInput.TryGet(out PlayerControllerB player2, out PlayerInputState state) || (Object)(object)player2 != (Object)(object)player)
			{
				reason = "o jogador não é mais o jogador local";
				return false;
			}
			if (!InputGate.CanUnloadScrap(in state, InBuildMode()))
			{
				reason = "o jogador está ocupado ou não pode agir";
				return false;
			}
			return CounterStillAccepts(player, mode, desk, out reason);
		}

		private static bool CanActNow(PlayerControllerB player, UnloadMode mode, DepositItemsDesk? desk, out string reason)
		{
			if (!LocalPlayerInput.TryGet(out PlayerControllerB player2, out PlayerInputState state) || (Object)(object)player2 != (Object)(object)player)
			{
				reason = "o jogador não é mais o jogador local";
				return false;
			}
			if (!state.CanAct || state.Busy || InBuildMode() || !SettledAfterSwitch(player))
			{
				reason = "o jogador está ocupado ou não pode agir";
				return false;
			}
			return CounterStillAccepts(player, mode, desk, out reason);
		}

		private static bool CounterStillAccepts(PlayerControllerB player, UnloadMode mode, DepositItemsDesk? desk, out string reason)
		{
			reason = "";
			if (mode != UnloadMode.PlaceOnCounter)
			{
				return true;
			}
			if ((Object)(object)desk == (Object)null || !InReach(desk, player))
			{
				reason = "o balcão saiu do alcance";
				return false;
			}
			if (desk.inGrabbingObjectsAnimation || ObjectsOnCounter(desk) >= 150)
			{
				reason = "o balcão não está aceitando itens agora";
				return false;
			}
			return true;
		}

		private static void RestoreSlot(PlayerControllerB player, int savedSlot)
		{
			if (player.currentItemSlot != savedSlot && (savedSlot == 50 || (savedSlot >= 0 && savedSlot < player.ItemSlots.Length)) && LocalPlayerInput.TryGet(out PlayerControllerB player2, out PlayerInputState state) && !((Object)(object)player2 != (Object)(object)player) && state.CanAct && !state.Busy && !state.TwoHanded && !InBuildMode())
			{
				SlotSwitcher.SwitchNow(player, savedSlot);
			}
		}

		private static bool SettledAfterSwitch(PlayerControllerB player)
		{
			if (!player.isGrabbingObjectAnimation)
			{
				return player.timeSinceSwitchingSlots >= 0.15f;
			}
			return false;
		}

		private static bool IsLocalPlayer(PlayerControllerB player)
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if ((Object)(object)player != (Object)null && (Object)(object)instance != (Object)null && (Object)(object)instance.localPlayerController == (Object)(object)player)
			{
				return !player.isPlayerDead;
			}
			return false;
		}

		private static bool HoldsScrapFrom(PlayerControllerB player, int slot)
		{
			if (slot < 0 || slot >= player.ItemSlots.Length || player.currentItemSlot != slot || !player.isHoldingObject)
			{
				return false;
			}
			GrabbableObject currentlyHeldObjectServer = player.currentlyHeldObjectServer;
			if ((Object)(object)currentlyHeldObjectServer != (Object)null && (Object)(object)player.ItemSlots[slot] == (Object)(object)currentlyHeldObjectServer)
			{
				return Classify(currentlyHeldObjectServer) == SlotItemKind.Scrap;
			}
			return false;
		}

		private static SlotItemKind[] ClassifySlots(PlayerControllerB player)
		{
			GrabbableObject[] itemSlots = player.ItemSlots;
			SlotItemKind[] array = new SlotItemKind[itemSlots.Length];
			for (int i = 0; i < itemSlots.Length; i++)
			{
				array[i] = Classify(itemSlots[i]);
			}
			return array;
		}

		private static SlotItemKind Classify(GrabbableObject? item)
		{
			if ((Object)(object)item == (Object)null)
			{
				return SlotItemKind.Empty;
			}
			if (!((Object)(object)item.itemProperties != (Object)null) || !item.itemProperties.isScrap)
			{
				return SlotItemKind.Tool;
			}
			return SlotItemKind.Scrap;
		}

		private static DepositItemsDesk? FindCounter()
		{
			int num = LoadedScenesKey();
			bool flag = _counter != null && (Object)(object)_counter == (Object)null;
			if (!_counterSearched || num != _counterScenesKey || flag)
			{
				_counter = Object.FindObjectOfType<DepositItemsDesk>();
				_counterScenesKey = num;
				_counterSearched = true;
			}
			if (!((Object)(object)_counter != (Object)null))
			{
				return null;
			}
			return _counter;
		}

		private static int LoadedScenesKey()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			int num = 17;
			for (int i = 0; i < SceneManager.sceneCount; i++)
			{
				int num2 = num * 31;
				Scene sceneAt = SceneManager.GetSceneAt(i);
				num = num2 + ((Scene)(ref sceneAt)).handle;
			}
			return num;
		}

		private static bool InReach(DepositItemsDesk desk, PlayerControllerB player)
		{
			//IL_0022: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)desk.triggerCollider != (Object)null && (Object)(object)player.localItemHolder != (Object)null)
			{
				Bounds bounds = ((Collider)desk.triggerCollider).bounds;
				return ((Bounds)(ref bounds)).Contains(player.localItemHolder.position);
			}
			return false;
		}

		private static int ObjectsOnCounter(DepositItemsDesk desk)
		{
			if (!((Object)(object)desk.deskObjectsContainer != (Object)null))
			{
				return int.MaxValue;
			}
			return ((Component)desk.deskObjectsContainer).GetComponentsInChildren<GrabbableObject>().Length;
		}

		private static void Stopped(string reason)
		{
			Plugin.Log.LogInfo((object)("Tirar a sucata dos slots parou no meio: " + reason + "."));
		}

		private static void Release(int id)
		{
			if (_runningId == id)
			{
				_runningId = 0;
				_runningOn = null;
			}
		}
	}
	internal static class LocalPlayerInput
	{
		private const int JetpackItemId = 13;

		public static bool TryGet(out PlayerControllerB player, out PlayerInputState state)
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			player = (((Object)(object)instance != (Object)null) ? instance.localPlayerController : null);
			if ((Object)(object)player == (Object)null)
			{
				state = default(PlayerInputState);
				return false;
			}
			state = new PlayerInputState(CanAct(player), IsBusy(player), player.twoHanded, player.timeSinceSwitchingSlots, player.currentItemSlot, player.ItemSlots.Length, HasAnyItem(player));
			return true;
		}

		private static bool CanAct(PlayerControllerB p)
		{
			if (((NetworkBehaviour)p).IsOwner && p.isPlayerControlled && !p.isPlayerDead)
			{
				if (((NetworkBehaviour)p).IsServer)
				{
					return p.isHostPlayerObject;
				}
				return true;
			}
			return false;
		}

		private static bool IsBusy(PlayerControllerB p)
		{
			if (!p.inTerminalMenu && !p.isTypingChat && (!((Object)(object)p.quickMenuManager != (Object)null) || !p.quickMenuManager.isMenuOpen) && !p.inSpecialMenu && !p.isGrabbingObjectAnimation && !p.inSpecialInteractAnimation && !p.throwingObject && !p.activatingItem)
			{
				return IsUsingJetpack(p);
			}
			return true;
		}

		private static bool IsUsingJetpack(PlayerControllerB p)
		{
			GrabbableObject currentlyHeldObjectServer = p.currentlyHeldObjectServer;
			if ((p.jetpackControls || p.disablingJetpackControls) && (Object)(object)currentlyHeldObjectServer != (Object)null)
			{
				return currentlyHeldObjectServer.itemProperties.itemId == 13;
			}
			return false;
		}

		private static bool HasAnyItem(PlayerControllerB p)
		{
			if ((Object)(object)p.ItemOnlySlot != (Object)null)
			{
				return true;
			}
			GrabbableObject[] itemSlots = p.ItemSlots;
			for (int i = 0; i < itemSlots.Length; i++)
			{
				if ((Object)(object)itemSlots[i] != (Object)null)
				{
					return true;
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "Update")]
	internal static class ShipRecharge
	{
		private sealed class Entry
		{
			public float LastSyncAt;

			public float LastSeenAt;
		}

		private const float FloorScanInterval = 1f;

		private const float PruneInterval = 10f;

		private const float StaleAfterSeconds = 5f;

		private static readonly Dictionary<int, Entry> Entries = new Dictionary<int, Entry>();

		private static readonly List<int> ToRemove = new List<int>();

		private static float _lastFloorScan;

		private static float _lastPrune;

		[HarmonyPostfix]
		private static void UpdatePostfix(PlayerControllerB __instance)
		{
			try
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)__instance != (Object)(object)instance.localPlayerController)
				{
					return;
				}
				PluginConfig cfg = Plugin.Cfg;
				if (!cfg.ShipRechargeEnabled.Value)
				{
					return;
				}
				float value = cfg.ShipRechargeSecondsToFull.Value;
				float time = Time.time;
				if (!__instance.isPlayerDead && __instance.isPlayerControlled && __instance.isInHangarShipRoom)
				{
					float deltaTime = Time.deltaTime;
					GrabbableObject[] itemSlots = __instance.ItemSlots;
					for (int i = 0; i < itemSlots.Length; i++)
					{
						RechargeCarried(itemSlots[i], deltaTime, value, time);
					}
					RechargeCarried(__instance.ItemOnlySlot, deltaTime, value, time);
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton != (Object)null && singleton.IsServer && time - _lastFloorScan >= 1f)
				{
					float val = time - _lastFloorScan;
					_lastFloorScan = time;
					RechargeFloorItems(Math.Min(val, 5f), value, time);
				}
				if (time - _lastPrune >= 10f)
				{
					_lastPrune = time;
					Prune(time);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha na recarga de baterias: {arg}");
			}
		}

		private static void RechargeCarried(GrabbableObject? item, float dt, float secondsToFull, float now)
		{
			if (!((Object)(object)item == (Object)null) && CanRecharge(item))
			{
				Step(item, dt, secondsToFull, now, ((NetworkBehaviour)item).IsOwner, isServerRpc: true);
			}
		}

		private static void RechargeFloorItems(float dt, float secondsToFull, float now)
		{
			GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
			foreach (GrabbableObject val in array)
			{
				if (!((Object)(object)val == (Object)null) && !val.isHeld && val.isInShipRoom && CanRecharge(val))
				{
					Step(val, dt, secondsToFull, now, canSend: true, isServerRpc: false);
				}
			}
		}

		private static bool CanRecharge(GrabbableObject item)
		{
			Item itemProperties = item.itemProperties;
			Battery insertedBattery = item.insertedBattery;
			if ((Object)(object)itemProperties != (Object)null && itemProperties.requiresBattery && insertedBattery != null && insertedBattery.charge < 1f)
			{
				return !item.isBeingUsed;
			}
			return false;
		}

		private static void Step(GrabbableObject item, float dt, float secondsToFull, float now, bool canSend, bool isServerRpc)
		{
			int instanceID = ((Object)item).GetInstanceID();
			if (!Entries.TryGetValue(instanceID, out Entry value))
			{
				value = new Entry
				{
					LastSyncAt = now - 1f
				};
				Entries[instanceID] = value;
			}
			value.LastSeenAt = now;
			Battery insertedBattery = item.insertedBattery;
			float num = (insertedBattery.charge = BatteryRecharge.Next(insertedBattery.charge, dt, secondsToFull));
			if (num > 0f)
			{
				insertedBattery.empty = false;
			}
			bool flag = num >= 1f;
			if (canSend && SyncThrottle.ShouldSync(now, value.LastSyncAt, flag))
			{
				value.LastSyncAt = now;
				int num2 = Mathf.Clamp(Mathf.RoundToInt(num * 100f), 0, 100);
				if (isServerRpc)
				{
					item.SyncBatteryServerRpc(num2);
				}
				else
				{
					item.SyncBatteryClientRpc(num2);
				}
			}
			if (flag)
			{
				Entries.Remove(instanceID);
			}
		}

		private static void Prune(float now)
		{
			ToRemove.Clear();
			foreach (KeyValuePair<int, Entry> entry in Entries)
			{
				if (now - entry.Value.LastSeenAt > 5f)
				{
					ToRemove.Add(entry.Key);
				}
			}
			foreach (int item in ToRemove)
			{
				Entries.Remove(item);
			}
		}
	}
	internal static class SlotHotkeys
	{
		public static void Bind(HotFixesInputs inputs)
		{
			inputs.Slot1.performed += delegate(CallbackContext context)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				OnSlotPressed(context, 0);
			};
			inputs.Slot2.performed += delegate(CallbackContext context)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				OnSlotPressed(context, 1);
			};
			inputs.Slot3.performed += delegate(CallbackContext context)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				OnSlotPressed(context, 2);
			};
			inputs.Slot4.performed += delegate(CallbackContext context)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				OnSlotPressed(context, 3);
			};
		}

		private static void OnSlotPressed(CallbackContext context, int slot)
		{
			if (((CallbackContext)(ref context)).performed && Plugin.Cfg.SlotHotkeysEnabled.Value)
			{
				SlotSwitcher.Request(slot);
			}
		}
	}
	[HarmonyPatch]
	internal static class SlotKeyLabels
	{
		private const string LabelName = "BitencolaSlotKey";

		private static bool _listeningToConfig;

		private static bool _lastUsingController;

		[HarmonyPatch(typeof(HUDManager), "SetUtilitySlotKeyText")]
		[HarmonyPostfix]
		private static void AfterUtilityKeyText(HUDManager __instance)
		{
			try
			{
				ListenToConfigOnce();
				Refresh(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar os números dos slots: {arg}");
			}
		}

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate(HUDManager __instance)
		{
			try
			{
				if (UsingController() != _lastUsingController)
				{
					Refresh(__instance);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao atualizar os números dos slots: {arg}");
			}
		}

		private static void ListenToConfigOnce()
		{
			if (!_listeningToConfig)
			{
				_listeningToConfig = true;
				Plugin.Cfg.ShowSlotNumbers.SettingChanged += delegate
				{
					RefreshCurrentHud();
				};
				Plugin.Cfg.SlotHotkeysEnabled.SettingChanged += delegate
				{
					RefreshCurrentHud();
				};
			}
		}

		private static void RefreshCurrentHud()
		{
			try
			{
				if ((Object)(object)HUDManager.Instance != (Object)null)
				{
					Refresh(HUDManager.Instance);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao atualizar os números dos slots: {arg}");
			}
		}

		private static bool UsingController()
		{
			if ((Object)(object)StartOfRound.Instance != (Object)null)
			{
				return StartOfRound.Instance.localPlayerUsingController;
			}
			return false;
		}

		private static void Refresh(HUDManager hud)
		{
			//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_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			bool usingController = (_lastUsingController = UsingController());
			TextMeshProUGUI utilitySlotKeyText = hud.utilitySlotKeyText;
			Image itemOnlySlotIconFrame = hud.itemOnlySlotIconFrame;
			Image[] itemSlotIconFrames = hud.itemSlotIconFrames;
			if ((Object)(object)utilitySlotKeyText == (Object)null || (Object)(object)itemOnlySlotIconFrame == (Object)null || itemSlotIconFrames == null)
			{
				return;
			}
			Vector3 val = ((Transform)((TMP_Text)utilitySlotKeyText).rectTransform).position - ((Transform)((Graphic)itemOnlySlotIconFrame).rectTransform).position;
			for (int i = 0; i < itemSlotIconFrames.Length; i++)
			{
				Image val2 = itemSlotIconFrames[i];
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				if (i >= 4)
				{
					Transform val3 = ((Transform)((Graphic)val2).rectTransform).Find("BitencolaSlotKey");
					if ((Object)(object)val3 != (Object)null)
					{
						((Component)val3).gameObject.SetActive(false);
					}
					continue;
				}
				TextMeshProUGUI val4 = HudLabels.FindOrCloneTabLabel(hud, (Transform)(object)((Graphic)val2).rectTransform, "BitencolaSlotKey", ((Transform)((Graphic)val2).rectTransform).position + val);
				if (!((Object)(object)val4 == (Object)null))
				{
					((TMP_Text)val4).text = SlotKeyLabel.Format(BindingDisplay(i), i);
					((Component)val4).gameObject.SetActive(SlotKeyLabel.IsVisible(i, Plugin.Cfg.ShowSlotNumbers.Value, Plugin.Cfg.SlotHotkeysEnabled.Value, usingController));
				}
			}
		}

		private static string? BindingDisplay(int slot)
		{
			try
			{
				return SlotKeyBindings.Display(slot);
			}
			catch (Exception)
			{
				return null;
			}
		}
	}
	internal static class SlotKeyBindings
	{
		private const string UnboundMarker = "Not-Bound";

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static string? Display(int slot)
		{
			//IL_004f: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			HotFixesInputs instance = HotFixesInputs.Instance;
			if (instance == null)
			{
				return null;
			}
			InputAction val = (InputAction)(slot switch
			{
				0 => instance.Slot1, 
				1 => instance.Slot2, 
				2 => instance.Slot3, 
				3 => instance.Slot4, 
				_ => null, 
			});
			if (val == null || val.bindings.Count == 0)
			{
				return null;
			}
			InputBinding val2 = val.bindings[0];
			if (((InputBinding)(ref val2)).effectivePath.Contains("Not-Bound"))
			{
				return "-";
			}
			return InputActionRebindingExtensions.GetBindingDisplayString(val, 0, (DisplayStringOptions)0);
		}
	}
	[HarmonyPatch]
	internal static class SlotSwitcher
	{
		private const float GrabSoundVolume = 0.6f;

		private const float MinCooldownSeconds = 0.05f;

		private static readonly PendingSlotRequest Pending = new PendingSlotRequest();

		public static void Request(int slot)
		{
			if (!LocalPlayerInput.TryGet(out PlayerControllerB player, out PlayerInputState state))
			{
				Pending.Clear();
				return;
			}
			switch (InputGate.DecideSwitch(in state, slot, Cooldown()))
			{
			case SwitchDecision.Switch:
				Pending.Clear();
				SwitchNow(player, slot);
				break;
			case SwitchDecision.Wait:
				Pending.Set(slot, Time.time);
				break;
			default:
				Pending.Clear();
				break;
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "Update")]
		[HarmonyPostfix]
		private static void ApplyPendingRequest(PlayerControllerB __instance)
		{
			try
			{
				float num = Cooldown();
				if (Pending.TryGet(Time.time, PendingSlotRequest.MaxAgeFor(num), out var slot) && LocalPlayerInput.TryGet(out PlayerControllerB player, out PlayerInputState state) && !((Object)(object)__instance != (Object)(object)player))
				{
					switch (InputGate.DecideSwitch(in state, slot, num))
					{
					case SwitchDecision.Switch:
						Pending.Clear();
						SwitchNow(player, slot);
						break;
					case SwitchDecision.Ignore:
						Pending.Clear();
						break;
					}
				}
			}
			catch (Exception arg)
			{
				Pending.Clear();
				Plugin.Log.LogError((object)$"Falha ao aplicar a troca de slot guardada: {arg}");
			}
		}

		private static float Cooldown()
		{
			return Mathf.Max(Plugin.Cfg.SlotSwitchCooldownSeconds.Value, 0.05f);
		}

		internal static void SwitchNow(PlayerControllerB player, int slot)
		{
			if ((Object)(object)ShipBuildModeManager.Instance != (Object)null)
			{
				ShipBuildModeManager.Instance.CancelBuildMode(true);
			}
			player.playerBodyAnimator.SetBool("GrabValidated", false);
			player.SwitchToItemSlot(slot, (GrabbableObject)null);
			player.SwitchToSlotServerRpc(slot);
			GrabbableObject currentlyHeldObjectServer = player.currentlyHeldObjectServer;
			if ((Object)(object)currentlyHeldObjectServer != (Object)null)
			{
				AudioSource component = ((Component)currentlyHeldObjectServer).GetComponent<AudioSource>();
				AudioClip grabSFX = currentlyHeldObjectServer.itemProperties.grabSFX;
				if ((Object)(object)component != (Object)null && (Object)(object)grabSFX != (Object)null)
				{
					component.PlayOneShot(grabSFX, 0.6f);
				}
			}
			player.timeSinceSwitchingSlots = 0f;
		}
	}
}
namespace BitencolaHotFixes.Features.Remains
{
	[HarmonyPatch(typeof(GrabbableObject), "Start")]
	internal static class CorpseHider
	{
		private static readonly Dictionary<int, EnemyAI> Hidden = new Dictionary<int, EnemyAI>();

		private static readonly List<int> Destroyed = new List<int>();

		private static readonly List<EnemyAI> Enemies = new List<EnemyAI>();

		private static readonly List<CorpseCandidate> Candidates = new List<CorpseCandidate>();

		internal static event Action<GrabbableObject, EnemyAI>? CorpseMatched;

		[HarmonyPostfix]
		private static void StartPostfix(GrabbableObject __instance)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			EnemyAI val = null;
			try
			{
				if (RemainsItems.IsRemains(__instance.itemProperties))
				{
					val = HideNearestCorpse(((Component)__instance).transform.position);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao esconder o corpo do monstro: {arg}");
			}
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				CorpseHider.CorpseMatched?.Invoke(__instance, val);
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogError((object)$"Falha ao avisar qual corpo a caixa de restos escondeu: {arg2}");
			}
		}

		private static EnemyAI? HideNearestCorpse(Vector3 box)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_004f: 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)
			//IL_008e: 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_009a: Unknown result type (might be due to invalid IL or missing references)
			ForgetDestroyed();
			Enemies.Clear();
			Candidates.Clear();
			EnemyAI[] array = Object.FindObjectsByType<EnemyAI>((FindObjectsSortMode)0);
			foreach (EnemyAI val in array)
			{
				Vector3 position = ((Component)val).transform.position;
				Enemies.Add(val);
				Candidates.Add(new CorpseCandidate(position.x, position.y, position.z, val.isEnemyDead, Hidden.ContainsKey(((Object)val).GetInstanceID())));
			}
			int num = CorpsePicker.PickNearest(Candidates, box.x, box.y, box.z);
			EnemyAI val2 = ((num >= 0) ? Enemies[num] : null);
			Enemies.Clear();
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogDebug((object)"Restos sem corpo por perto para esconder.");
				return null;
			}
			Hidden[((Object)val2).GetInstanceID()] = val2;
			Hide(val2);
			Plugin.Log.LogInfo((object)("Corpo escondido: " + (((Object)(object)val2.enemyType != (Object)null) ? val2.enemyType.enemyName : ((Object)val2).name)));
			return val2;
		}

		private static void Hide(EnemyAI enemy)
		{
			enemy.overrideSettingEnemyMeshes = true;
			Renderer[] componentsInChildren = ((Component)enemy).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (BelongsToEnemy((Component)(object)val))
				{
					val.enabled = false;
				}
			}
			Collider[] componentsInChildren2 = ((Component)enemy).GetComponentsInChildren<Collider>(true);
			foreach (Collider val2 in componentsInChildren2)
			{
				if (BelongsToEnemy((Component)(object)val2))
				{
					val2.enabled = false;
				}
			}
		}

		private static bool BelongsToEnemy(Component component)
		{
			if ((Object)(object)component.GetComponentInParent<GrabbableObject>(true) == (Object)null && (Object)(object)component.GetComponentInParent<PlayerControllerB>(true) == (Object)null)
			{
				return (Object)(object)component.GetComponentInParent<DeadBodyInfo>(true) == (Object)null;
			}
			return false;
		}

		private static void ForgetDestroyed()
		{
			Destroyed.Clear();
			foreach (KeyValuePair<int, EnemyAI> item in Hidden)
			{
				if ((Object)(object)item.Value == (Object)null)
				{
					Destroyed.Add(item.Key);
				}
			}
			foreach (int item2 in Destroyed)
			{
				Hidden.Remove(item2);
			}
		}
	}
	[HarmonyPatch]
	internal static class EnemyDeathPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(EnemyAI), "KillEnemy")]
		private static void KillEnemyPostfix(EnemyAI __instance, bool destroy)
		{
			RemainsSpawner.OnEnemyKilled(__instance, destroy);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(RoundManager), "UnloadSceneObjectsEarly")]
		private static void UnloadSceneObjectsEarlyPostfix()
		{
			RemainsSpawner.OnRoundEnded();
		}
	}
	[HarmonyPatch]
	internal static class KillCreditPatches
	{
		internal static readonly KillCredits Credits = new KillCredits();

		internal static readonly DailyScoreboard Scoreboard = new DailyScoreboard();

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

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

		public static void Register()
		{
			CorpseHider.CorpseMatched += OnCorpseMatched;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(EnemyAI), "HitEnemy")]
		private static void HitEnemyPostfix(EnemyAI __instance, PlayerControllerB? playerWhoHit)
		{
			try
			{
				if (!((Object)(object)playerWhoHit == (Object)null))
				{
					bool isEnemyDead = __instance.isEnemyDead;
					bool lateHitAllowed = isEnemyDead && KillCredits.IsLateHitAllowed(((NetworkBehaviour)__instance).IsOwner, ((NetworkBehaviour)playerWhoHit).OwnerClientId, ((NetworkBehaviour)__instance).OwnerClientId);
					Credits.RecordHit(((Object)__instance).GetInstanceID(), playerWhoHit.playerUsername, isEnemyDead, lateHitAllowed, Time.realtimeSinceStartup);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao registrar o golpe no monstro: {arg}");
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(EnemyAI), "KillEnemy")]
		private static void KillEnemyPostfix(EnemyAI __instance)
		{
			try
			{
				if (__instance.isEnemyDead)
				{
					Credits.MarkDead(((Object)__instance).GetInstanceID(), Time.realtimeSinceStartup);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao registrar a morte do monstro: {arg}");
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(GrabbableObject), "SetScrapValue")]
		private static void SetScrapValuePostfix(GrabbableObject __instance, int setValueTo)
		{
			try
			{
				if (RemainsItems.IsRemains(__instance.itemProperties))
				{
					int instanceID = ((Object)__instance).GetInstanceID();
					ValueByBox[instanceID] = setValueTo;
					if (KillerByBox.TryGetValue(instanceID, out string value))
					{
						ApplyKiller(__instance, setValueTo, value);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar quem matou o monstro: {arg}");
			}
		}

		public static void OnShipLeft()
		{
			Scoreboard.NewDay();
		}

		public static void OnDayEnded()
		{
			Credits.Clear();
			ValueByBox.Clear();
			Scoreboard.NewDay();
		}

		public static void OnSessionStarted()
		{
			Credits.Clear();
			ValueByBox.Clear();
			KillerByBox.Clear();
			Scoreboard.Clear();
		}

		private static void OnCorpseMatched(GrabbableObject box, EnemyAI corpse)
		{
			try
			{
				string text = Credits.KillerOf(((Object)corpse).GetInstanceID());
				if (text != null)
				{
					int instanceID = ((Object)box).GetInstanceID();
					KillerByBox[instanceID] = text;
					if (ValueByBox.TryGetValue(instanceID, out var value))
					{
						ApplyKiller(box, value, text);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao ligar a caixa de restos a quem matou o monstro: {arg}");
			}
		}

		private static void ApplyKiller(GrabbableObject box, int value, string killer)
		{
			if (Plugin.Cfg.ShowKillerEnabled.Value)
			{
				ScanNodeProperties componentInChildren = ((Component)box).GetComponentInChildren<ScanNodeProperties>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					componentInChildren.subText = RemainsScanText.Format(value, killer);
				}
			}
			StartOfRound instance = StartOfRound.Instance;
			int instanceID = ((Object)box).GetInstanceID();
			if ((Object)(object)instance != (Object)null && !instance.shipIsLeaving)
			{
				Scoreboard.TryAdd(instanceID, killer, value);
			}
			else
			{
				Scoreboard.Exclude(instanceID);
			}
		}
	}
	internal static class RemainsItems
	{
		private const int ItemIdBase = 41000;

		private const string PrefabNamePrefix = "BitencolaRemains_";

		private const int FallbackPropsLayer = 6;

		private const int FallbackScanNodeLayer = 22;

		private static readonly Dictionary<MonsterEntry, Item> ItemsByEntry = new Dictionary<MonsterEntry, Item>();

		private static readonly HashSet<Item> AllItems = new HashSet<Item>();

		public static IEnumerable<Item> All => AllItems;

		public static void CreateAndRegister()
		{
			if (ItemsByEntry.Count <= 0)
			{
				int propsLayer = ResolveLayer("Props", 6);
				int scanNodeLayer = ResolveLayer("ScanNode", 22);
				(Mesh mesh, Material? material) tuple = CreateBoxVisuals();
				Mesh item = tuple.mesh;
				Dictionary<MonsterTier, Material> dictionary = CreateTierMaterials(tuple.material);
				IReadOnlyList<MonsterEntry> entries = MonsterCatalog.Entries;
				for (int i = 0; i < entries.Count; i++)
				{
					MonsterEntry monsterEntry = entries[i];
					TierSpec tier = MonsterCatalog.Tiers[monsterEntry.Tier];
					Item val = CreateItem(monsterEntry, tier, 41000 + i);
					val.spawnPrefab = CreatePrefab(monsterEntry, tier, val, item, dictionary[monsterEntry.Tier], propsLayer, scanNodeLayer);
					Items.RegisterItem(val);
					ItemsByEntry.Add(monsterEntry, val);
					AllItems.Add(val);
				}
				Plugin.Log.LogInfo((object)$"{ItemsByEntry.Count} itens de restos de monstro registrados");
			}
		}

		public static Item ItemFor(MonsterEntry entry)
		{
			return ItemsByEntry[entry];
		}

		public static bool IsRemains(Item? item)
		{
			if ((Object)(object)item != (Object)null)
			{
				return AllItems.Contains(item);
			}
			return false;
		}

		private static Item CreateItem(MonsterEntry entry, TierSpec tier, int itemId)
		{
			Item obj = ScriptableObject.CreateInstance<Item>();
			((Object)obj).name = "BitencolaRemains_" + entry.Key;
			((Object)obj).hideFlags = (HideFlags)32;
			obj.itemName = entry.ItemName;
			obj.itemId = itemId;
			obj.isScrap = true;
			obj.weight = ItemWeight.ToGameWeight(tier.WeightLb);
			obj.twoHanded = tier.TwoHanded;
			obj.twoHandedAnimation = tier.TwoHanded;
			obj.minValue = tier.MinValue;
			obj.maxValue = tier.MaxValue;
			obj.itemSpawnsOnGround = true;
			obj.canBeGrabbedBeforeGameStart = true;
			obj.requiresBattery = false;
			obj.verticalOffset = tier.BoxSize / 2f;
			obj.toolTips = Array.Empty<string>();
			obj.meshVariants = Array.Empty<Mesh>();
			obj.materialVariants = Array.Empty<Material>();
			obj.clinkAudios = Array.Empty<AudioClip>();
			obj.spawnPositionTypes = new List<ItemGroup>();
			return obj;
		}

		private static GameObject CreatePrefab(MonsterEntry entry, TierSpec tier, Item item, Mesh mesh, Material? material, int propsLayer, int scanNodeLayer)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Expected O, but got Unknown
			GameObject obj = NetworkPrefabs.CreateNetworkPrefab("BitencolaRemains_" + entry.Key);
			obj.tag = "PhysicsProp";
			obj.layer = propsLayer;
			obj.transform.localScale = Vector3.one * tier.BoxSize;
			NetworkObject component = obj.GetComponent<NetworkObject>();
			component.AutoObjectParentSync = false;
			component.DontDestroyWithOwner = true;
			obj.AddComponent<MeshFilter>().sharedMesh = mesh;
			MeshRenderer val = obj.AddComponent<MeshRenderer>();
			((Renderer)val).sharedMaterial = material;
			obj.AddComponent<BoxCollider>();
			AudioSource obj2 = obj.AddComponent<AudioSource>();
			obj2.playOnAwake = false;
			obj2.spatialBlend = 1f;
			PhysicsProp obj3 = obj.AddComponent<PhysicsProp>();
			((GrabbableObject)obj3).itemProperties = item;
			((GrabbableObject)obj3).grabbable = true;
			((GrabbableObject)obj3).mainObjectRenderer = val;
			((GrabbableObject)obj3).insertedBattery = new Battery(true, 0f);
			AddScanNode(obj, item, scanNodeLayer);
			Utilities.FixMixerGroups(obj);
			return obj;
		}

		private static void AddScanNode(GameObject prefab, Item item, int scanNodeLayer)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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)
			GameObject val = new GameObject("ScanNode");
			val.transform.SetParent(prefab.transform, false);
			val.layer = scanNodeLayer;
			((Collider)val.AddComponent<BoxCollider>()).isTrigger = true;
			ScanNodeProperties obj = val.AddComponent<ScanNodeProperties>();
			obj.headerText = item.itemName;
			obj.subText = "Value: $0";
			obj.nodeType = 2;
			obj.minRange = 1;
			obj.maxRange = 13;
			obj.requiresLineOfSight = true;
		}

		private static Dictionary<MonsterTier, Material?> CreateTierMaterials(Material? template)
		{
			//IL_0043: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<MonsterTier, Material> dictionary = new Dictionary<MonsterTier, Material>();
			Color val2 = default(Color);
			foreach (MonsterTier value in Enum.GetValues(typeof(MonsterTier)))
			{
				if ((Object)(object)template == (Object)null)
				{
					dictionary[value] = null;
					continue;
				}
				Material val = new Material(template)
				{
					name = "BitencolaRemains_" + value,
					hideFlags = (HideFlags)32
				};
				var (num, num2, num3) = TierColors.For(value);
				((Color)(ref val2))..ctor(num, num2, num3);
				if (val.HasProperty("_BaseColor"))
				{
					val.SetColor("_BaseColor", val2);
				}
				else if (val.HasProperty("_Color"))
				{
					val.color = val2;
				}
				dictionary[value] = val;
			}
			if ((Object)(object)template != (Object)null)
			{
				Object.Destroy((Object)(object)template);
			}
			return dictionary;
		}

		private static (Mesh mesh, Material? material) CreateBoxVisuals()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3);
			Mesh sharedMesh = obj.GetComponent<MeshFilter>().sharedMesh;
			Material sharedMaterial = ((Renderer)obj.GetComponent<MeshRenderer>()).sharedMaterial;
			Object.Destroy((Object)(object)obj);
			Material val = null;
			Shader val2 = Shader.Find("HDRP/Lit");
			if ((Object)(object)val2 != (Object)null)
			{
				val = new Material(val2);
			}
			else
			{
				if (!((Object)(object)sharedMaterial != (Object)null))
				{
					Plugin.Log.LogError((object)"Shader HDRP/Lit não encontrado e sem material padrão; os restos ficam sem material.");
					return (mesh: sharedMesh, material: null);
				}
				Plugin.Log.LogWarning((object)"Shader HDRP/Lit não encontrado; os restos usam o material padrão.");
				val = new Material(sharedMaterial);
			}
			return (mesh: sharedMesh, material: val);
		}

		private static int ResolveLayer(string name, int fallback)
		{
			int num = LayerMask.NameToLayer(name);
			if (num >= 0)
			{
				return num;
			}
			Plugin.Log.LogError((object)$"Layer \"{name}\" não encontrado (NameToLayer devolveu -1); usando {fallback}.");
			return fallback;
		}
	}
	[HarmonyPatch(typeof(RoundManager), "SyncScrapValuesClientRpc")]
	internal static class RemainsScrapSyncPatch
	{
		internal sealed class SavedTotals
		{
			public float TotalScrapValueInLevel;

			public int ScrapCollectedInLevel;

			public int ValueOfFoundScrapItems;

			public int RemainsValue;
		}

		[HarmonyPrefix]
		private static void Prefix(RoundManager __instance, NetworkObjectReference[] spawnedScrap, int[] allScrapValue, out SavedTotals? __state)
		{
			__state = null;
			try
			{
				if (TrySumOnlyRemains(spawnedScrap, allScrapValue, out var sum))
				{
					__state = new SavedTotals
					{
						TotalScrapValueInLevel = __instance.totalScrapValueInLevel,
						ScrapCollectedInLevel = __instance.scrapCollectedInLevel,
						ValueOfFoundScrapItems = __instance.valueOfFoundScrapItems,
						RemainsValue = sum
					};
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao preservar os contadores de sucata: {arg}");
			}
		}

		[HarmonyPostfix]
		private static void Postfix(RoundManager __instance, SavedTotals? __state)
		{
			if (__state != null)
			{
				__instance.totalScrapValueInLevel = __state.TotalScrapValueInLevel + (float)__state.RemainsValue;
				__instance.scrapCollectedInLevel = __state.ScrapCollectedInLevel;
				__instance.valueOfFoundScrapItems = __state.ValueOfFoundScrapItems;
			}
		}

		private static bool TrySumOnlyRemains(NetworkObjectReference[]? spawnedScrap, int[]? values, out int sum)
		{
			sum = 0;
			if (spawnedScrap == null || values == null || spawnedScrap.Length == 0 || values.Length < spawnedScrap.Length)
			{
				return false;
			}
			NetworkObject val = default(NetworkObject);
			for (int i = 0; i < spawnedScrap.Length; i++)
			{
				if (!((NetworkObjectReference)(ref spawnedScrap[i])).TryGet(ref val, (NetworkManager)null) || (Object)(object)val == (Object)null)
				{
					return false;
				}
				GrabbableObject component = ((Component)val).GetComponent<GrabbableObject>();
				if ((Object)(object)component == (Object)null || !RemainsItems.IsRemains(component.itemProperties))
				{
					return false;
				}
				sum += values[i];
			}
			return true;
		}
	}
	internal static class RemainsSpawner
	{
		private const float DeathAnimationSeconds = 3f;

		private const float ValueSyncDelaySeconds = 1f;

		private const float SpawnHeight = 0.5f;

		private static readonly RemainsLedger Ledger = new RemainsLedger();

		private static readonly Random Rng = new Random();

		private static int _round;

		public static void OnEnemyKilled(EnemyAI enemy, bool destroyed)
		{
			try
			{
				HandleKill(enemy, destroyed);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao criar restos de monstro: {arg}");
			}
		}

		public static void OnRoundEnded()
		{
			Ledger.Clear();
			_round++;
		}

		private static void HandleKill(EnemyAI enemy, bool destroyed)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			EnemyType enemyType = enemy.enemyType;
			if ((Object)(object)enemyType == (Object)null)
			{
				return;
			}
			RemainsTiming remainsTiming = RemainsDeathRule.Decide(IsHost(), Plugin.Cfg.RemainsEnabled.Value, enemyType.canDie, destroyed, enemyType.canBeDestroyed, enemyType.destroyOnDeath);
			if (remainsTiming != RemainsTiming.None && Ledger.TryClaim(LedgerKey(enemy)))
			{
				MonsterEntry monsterEntry = MonsterCatalog.Resolve(TypeNameChain(((object)enemy).GetType()));
				if (remainsTiming == RemainsTiming.Immediately)
				{
					Spawn(monsterEntry, ((Component)enemy).transform.position, !enemy.isOutside);
				}
				else
				{
					StartOnRoundManager(SpawnAfterDeathAnimation(enemy, monsterEntry, _round), "restos de " + monsterEntry.DisplayName + " não criados");
				}
			}
		}

		private static IEnumerator SpawnAfterDeathAnimation(EnemyAI enemy, MonsterEntry entry, int round)
		{
			Vector3 position = ((Component)enemy).transform.position;
			bool inFactory = !enemy.isOutside;
			yield return (object)new WaitForSeconds(3f);
			try
			{
				SpawnAtCorpse(enemy, entry, round, position, inFactory);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao criar restos de monstro: {arg}");
			}
		}

		private static void SpawnAtCorpse(EnemyAI enemy, MonsterEntry entry, int round, Vector3 position, bool inFactory)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (round == _round && IsHost())
			{
				if ((Object)(object)enemy != (Object)null)
				{
					position = ((Component)enemy).transform.position;
					inFactory = !enemy.isOutside;
				}
				Spawn(entry, position, inFactory);
			}
		}

		private static void Spawn(MonsterEntry entry, Vector3 position, bool inFactory)
		{
			//IL_003c: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Plugin.Log.LogError((object)("RoundManager ausente: restos de " + entry.DisplayName + " não criados."));
				return;
			}
			Item val = RemainsItems.ItemFor(entry);
			GameObject obj = Object.Instantiate<GameObject>(val.spawnPrefab, position + Vector3.up * 0.5f, Quaternion.identity, instance.spawnedScrapContainer);
			RemainsVisibility.MakeFindable(obj);
			GrabbableObject component = obj.GetComponent<GrabbableObject>();
			component.isInFactory = inFactory;
			(int min, int max) tierRange = Plugin.Cfg.GetTierRange(entry.Tier);
			int item = tierRange.min;
			int item2 = tierRange.max;
			int num = (component.scrapValue = ValueRoller.Roll(item, item2, Rng));
			NetworkObject component2 = obj.GetComponent<NetworkObject>();
			component2.Spawn(false);
			Plugin.Log.LogInfo((object)$"Restos criados: {val.itemName} (${num})");
			StartOnRoundManager(SyncValueLater(component2, num), $"valor de {val.itemName} (${num}) não sincronizado; os outros jogadores veem $0");
		}

		private static IEnumerator SyncValueLater(NetworkObject remains, int value)
		{
			yield return (object)new WaitForSeconds(1f);
			try
			{
				RoundManager instance = RoundManager.Instance;
				if ((Object)(object)remains != (Object)null && remains.IsSpawned && (Object)(object)instance != (Object)null && IsHost())
				{
					instance.SyncScrapValuesClientRpc((NetworkObjectReference[])(object)new NetworkObjectReference[1] { NetworkObjectReference.op_Implicit(remains) }, new int[1] { value });
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao sincronizar o valor dos restos de monstro: {arg}");
			}
		}

		private static void StartOnRoundManager(IEnumerator routine, string consequence)
		{
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Plugin.Log.LogError((object)("RoundManager ausente: " + consequence + "."));
				return;
			}
			Coroutine val;
			try
			{
				val = ((MonoBehaviour)instance).StartCoroutine(routine);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao iniciar corrotina: {consequence}. {arg}");
				return;
			}
			if (val == null)
			{
				Plugin.Log.LogError((object)("Corrotina não iniciada: " + consequence + "."));
			}
		}

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

		private static ulong LedgerKey(EnemyAI enemy)
		{
			return (ulong)((Object)enemy).GetInstanceID();
		}

		private static IEnumerable<string> TypeNameChain(Type type)
		{
			Type t = type;
			while (t != null && t != typeof(EnemyAI))
			{
				yield return t.Name;
				t = t.BaseType;
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Awake")]
	internal static class RemainsTemplatePatch
	{
		[HarmonyPostfix]
		private static void AwakePostfix(StartOfRound __instance)
		{
			try
			{
				ApplyTemplates(__instance.allItemsList.itemsList);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao copiar ícone e sons para os restos de monstro: {arg}");
			}
		}

		private static void ApplyTemplates(List<Item> allItems)
		{
			Item val = FindVanillaScrap(allItems, twoHanded: false);
			Item val2 = FindVanillaScrap(allItems, twoHanded: true);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Sucata do jogo para servir de modelo não encontrada; alguns restos ficam sem ícone/sons.");
			}
			foreach (Item item in RemainsItems.All)
			{
				Item val3 = (item.twoHanded ? val2 : val);
				if ((Object)(object)val3 != (Object)null)
				{
					CopyFromTemplate(val3, item);
				}
			}
		}

		private static Item? FindVanillaScrap(List<Item> allItems, bool twoHanded)
		{
			Item val = null;
			foreach (Item allItem in allItems)
			{
				if (!((Object)(object)allItem == (Object)null) && allItem.isScrap && allItem.twoHanded == twoHanded && !((Object)(object)allItem.spawnPrefab == (Object)null) && !RemainsItems.IsRemains(allItem) && !Items.LethalLibItemList.Contains(allItem))
				{
					GrabbableObject component = allItem.spawnPrefab.GetComponent<GrabbableObject>();
					if ((Object)(object)component != (Object)null && ((object)component).GetType() == typeof(PhysicsProp))
					{
						return allItem;
					}
					if (val == null)
					{
						val = allItem;
					}
				}
			}
			return val;
		}

		private static void CopyFromTemplate(Item template, Item item)
		{
			//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)
			item.itemIcon = template.itemIcon;
			item.grabSFX = template.grabSFX;
			item.dropSFX = template.dropSFX;
			item.pocketSFX = template.pocketSFX;
			item.throwSFX = template.throwSFX;
			item.grabAnim = template.grabAnim;
			item.restingRotation = template.restingRotation;
			CopyAudioSettings(template.spawnPrefab, item.spawnPrefab);
		}

		private static void CopyAudioSettings(GameObject templatePrefab, GameObject prefab)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			AudioSource component = templatePrefab.GetComponent<AudioSource>();
			AudioSource component2 = prefab.GetComponent<AudioSource>();
			if (!((Object)(object)component == (Object)null) && !((Object)(object)component2 == (Object)null))
			{
				component2.outputAudioMixerGroup = component.outputAudioMixerGroup;
				component2.spatialBlend = component.spatialBlend;
				component2.dopplerLevel = component.dopplerLevel;
				component2.rolloffMode = component.rolloffMode;
				component2.minDistance = component.minDistance;
				component2.maxDistance = component.maxDistance;
				if ((int)component.rolloffMode == 2)
				{
					component2.SetCustomCurve((AudioSourceCurveType)0, component.GetCustomCurve((AudioSourceCurveType)0));
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class RemainsVisibility
	{
		public static void MakeFindable(GameObject root)
		{
			Transform[] componentsInChildren = root.GetComponentsInChildren<Transform>(true);
			foreach (Transform obj in componentsInChildren)
			{
				((Object)((Component)obj).gameObject).hideFlags = (HideFlags)0;
				Component[] components = ((Component)obj).GetComponents<Component>();
				foreach (Component val in components)
				{
					if ((Object)(object)val != (Object)null)
					{
						((Object)val).hideFlags = (HideFlags)0;
					}
				}
			}
		}

		[HarmonyPatch(typeof(GrabbableObject), "Start")]
		[HarmonyPostfix]
		private static void StartPostfix(GrabbableObject __instance)
		{
			try
			{
				if ((Object)(object)__instance != (Object)null && RemainsItems.IsRemains(__instance.itemProperties))
				{
					MakeFindable(((Component)__instance).gameObject);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao tornar os restos visíveis para o jogo: {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class RemainsWipePatch
	{
		[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
		[HarmonyPrefix]
		private static void BeforeDespawnProps(RoundManager __instance, bool despawnAllItems)
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).IsServer && !((Object)(object)instance == (Object)null))
				{
					Plugin.Log.LogInfo((object)$"Limpeza de fim de rodada: todos mortos={instance.allPlayersDead}, apagar tudo={despawnAllItems}, restos de monstro={CountRemains()}");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao registrar a limpeza de fim de rodada: {arg}");
			}
		}

		[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
		[HarmonyFinalizer]
		private static void AfterDespawnProps(Exception? __exception)
		{
			if (__exception != null)
			{
				Plugin.Log.LogError((object)$"A limpeza de itens do jogo falhou no fim da rodada: {__exception}");
			}
		}

		private static int CountRemains()
		{
			int num = 0;
			GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
			foreach (GrabbableObject val in array)
			{
				if ((Object)(object)val != (Object)null && RemainsItems.IsRemains(val.itemProperties))
				{
					num++;
				}
			}
			return num;
		}
	}
	[HarmonyPatch]
	internal static class ScoreboardPatch
	{
		private const string Header = "Placar do dia";

		private const string TipSource = "placar";

		[HarmonyPostfix]
		[HarmonyPatch(typeof(StartOfRound), "ShipLeave")]
		private static void ShipLeavePostfix()
		{
			try
			{
				if (Plugin.Cfg.DailyScoreboardEnabled.Value)
				{
					string text = KillCreditPatches.Scoreboard.Format();
					if (text.Length > 0)
					{
						TipQueue.Show("placar", "Placar do dia", text);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar o placar do dia: {arg}");
			}
			finally
			{
				ClearSafely(KillCreditPatches.OnShipLeft);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(StartOfRound), "EndOfGameClientRpc")]
		private static void EndOfGameClientRpcPostfix()
		{
			ClearSafely(KillCreditPatches.OnDayEnded);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(StartOfRound), "Start")]
		private static void StartPostfix()
		{
			ClearSafely(KillCreditPatches.OnSessionStarted);
		}

		private static void ClearSafely(Action clear)
		{
			try
			{
				clear();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao zerar o placar do dia: {arg}");
			}
		}
	}
}
namespace BitencolaHotFixes.Features.Hud
{
	[HarmonyPatch]
	internal static class HealthLabel
	{
		private const string LabelName = "BitencolaHealthText";

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate(HUDManager __instance)
		{
			try
			{
				Refresh(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar a vida em números: {arg}");
			}
		}

		private static void Refresh(HUDManager hud)
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			CanvasGroup selfRedCanvasGroup = hud.selfRedCanvasGroup;
			if ((Object)(object)selfRedCanvasGroup == (Object)null)
			{
				return;
			}
			Transform parent = ((Component)selfRedCanvasGroup).transform.parent;
			if ((Object)(object)parent == (Object)null)
			{
				return;
			}
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			if (!Plugin.Cfg.ShowHealthNumbers.Value || !((Object)(object)val != (Object)null) || val.isPlayerDead)
			{
				Transform val2 = parent.Find("BitencolaHealthText");
				if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeSelf)
				{
					((Component)val2).gameObject.SetActive(false);
				}
				return;
			}
			Vector3[] array = (Vector3[])(object)new Vector3[4];
			((RectTransform)((Component)selfRedCanvasGroup).transform).GetWorldCorners(array);
			Vector3 worldPosition = (array[0] + array[3]) * 0.5f;
			TextMeshProUGUI val3 = HudLabels.FindOrCloneTabLabel(hud, parent, "BitencolaHealthText", worldPosition);
			if (!((Object)(object)val3 == (Object)null))
			{
				string text = HudText.Health(val.health);
				if (((TMP_Text)val3).text != text)
				{
					((TMP_Text)val3).text = text;
				}
				if (!((Component)val3).gameObject.activeSelf)
				{
					((Component)val3).gameObject.SetActive(true);
				}
			}
		}
	}
	internal static class HudLabels
	{
		public static TextMeshProUGUI? FindOrCloneTabLabel(HUDManager hud, Transform parent, string name, Vector3 worldPosition)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			Transform val = parent.Find(name);
			if ((Object)(object)val != (Object)null)
			{
				return ((Component)val).GetComponent<TextMeshProUGUI>();
			}
			TextMeshProUGUI utilitySlotKeyText = hud.utilitySlotKeyText;
			if ((Object)(object)utilitySlotKeyText == (Object)null)
			{
				return null;
			}
			TextMeshProUGUI obj = Object.Instantiate<TextMeshProUGUI>(utilitySlotKeyText, parent, true);
			((Object)obj).name = name;
			((Transform)((TMP_Text)obj).rectTransform).position = worldPosition;
			((TMP_Text)obj).enableWordWrapping = false;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)0;
			return obj;
		}
	}
	[HarmonyPatch]
	internal static class SlotValueLabels
	{
		private const string LabelName = "BitencolaSlotValue";

		private const int SlotCount = 4;

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate(HUDManager __instance)
		{
			try
			{
				Refresh(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar o valor nos slots: {arg}");
			}
		}

		private static void Refresh(HUDManager hud)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: 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_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: 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)
			Image[] itemSlotIconFrames = hud.itemSlotIconFrames;
			Image itemOnlySlotIconFrame = hud.itemOnlySlotIconFrame;
			TextMeshProUGUI utilitySlotKeyText = hud.utilitySlotKeyText;
			if (itemSlotIconFrames == null || (Object)(object)itemOnlySlotIconFrame == (Object)null || (Object)(object)utilitySlotKeyText == (Object)null)
			{
				return;
			}
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			GrabbableObject[] array = (((Object)(object)val != (Object)null) ? val.ItemSlots : null);
			bool flag = Plugin.Cfg.ShowSlotValues.Value && array != null;
			Vector3 val2 = ((Transform)((TMP_Text)utilitySlotKeyText).rectTransform).position - ((Transform)((Graphic)itemOnlySlotIconFrame).rectTransform).position;
			Vector3 val3 = default(Vector3);
			((Vector3)(ref val3))..ctor(val2.x, 0f - val2.y, val2.z);
			for (int i = 0; i < 4 && i < itemSlotIconFrames.Length; i++)
			{
				Image val4 = itemSlotIconFrames[i];
				if ((Object)(object)val4 == (Object)null)
				{
					continue;
				}
				string text = "";
				if (flag && array != null && i < array.Length)
				{
					GrabbableObject val5 = array[i];
					if ((Object)(object)val5 != (Object)null && (Object)(object)val5.itemProperties != (Object)null)
					{
						text = HudText.ScrapValue(val5.scrapValue, val5.itemProperties.isScrap);
					}
				}
				if (text.Length == 0)
				{
					Transform val6 = ((Transform)((Graphic)val4).rectTransform).Find("BitencolaSlotValue");
					if ((Object)(object)val6 != (Object)null && ((Component)val6).gameObject.activeSelf)
					{
						((Component)val6).gameObject.SetActive(false);
					}
					continue;
				}
				TextMeshProUGUI val7 = HudLabels.FindOrCloneTabLabel(hud, (Transform)(object)((Graphic)val4).rectTransform, "BitencolaSlotValue", ((Transform)((Graphic)val4).rectTransform).position + val3);
				if (!((Object)(object)val7 == (Object)null))
				{
					if (((TMP_Text)val7).text != text)
					{
						((TMP_Text)val7).text = text;
					}
					if (!((Component)val7).gameObject.activeSelf)
					{
						((Component)val7).gameObject.SetActive(true);
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "Update")]
	internal static class TipQueue
	{
		private sealed class Tip
		{
			public string Source { get; }

			public string Header { get; set; }

			public string Body { get; set; }

			public Tip(string source, string header, string body)
			{
				Source = source;
				Header = header;
				Body = body;
			}
		}

		public const float MinSecondsOnScreen = 5f;

		private static readonly List<Tip> Pending = new List<Tip>();

		private static HUDManager? _hud;

		private static float _lastShownAt = float.NegativeInfinity;

		private static string? _lastShownSource;

		public static void Show(string source, string header, string body)
		{
			try
			{
				HUDManager instance = HUDManager.Instance;
				ResetIfNewHud(instance);
				for (int i = 0; i < Pending.Count; i++)
				{
					if (Pending[i].Source == source)
					{
						Pending[i].Header = header;
						Pending[i].Body = body;
						return;
					}
				}
				float unscaledTime = Time.unscaledTime;
				bool flag = ((unscaledTime - _lastShownAt < 5f) ? (_lastShownSource == source) : (Pending.Count == 0));
				if ((Object)(object)instance != (Object)null && flag)
				{
					Display(instance, source, header, body, unscaledTime);
				}
				else
				{
					Pending.Add(new Tip(source, header, body));
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar um aviso na tela: {arg}");
			}
		}

		public static bool IsQueuedOrOnScreen(string source)
		{
			for (int i = 0; i < Pending.Count; i++)
			{
				if (Pending[i].Source == source)
				{
					return true;
				}
			}
			if (_lastShownSource == source)
			{
				return Time.unscaledTime - _lastShownAt < 5f;
			}
			return false;
		}

		[HarmonyPostfix]
		private static void UpdatePostfix(HUDManager __instance)
		{
			try
			{
				ResetIfNewHud(__instance);
				if (Pending.Count != 0)
				{
					float unscaledTime = Time.unscaledTime;
					if (!(unscaledTime - _lastShownAt < 5f))
					{
						Tip tip = Pending[0];
						Pending.RemoveAt(0);
						Display(__instance, tip.Source, tip.Header, tip.Body, unscaledTime);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar um aviso da fila: {arg}");
			}
		}

		private static void ResetIfNewHud(HUDManager? hud)
		{
			if (!((Object)(object)hud == (Object)null) && hud != _hud)
			{
				if (_hud != null)
				{
					Pending.Clear();
					_lastShownAt = float.NegativeInfinity;
					_lastShownSource = null;
				}
				_hud = hud;
			}
		}

		private static void Display(HUDManager hud, string source, string header, string body, float now)
		{
			hud.DisplayTip(header, body, false, false, "LC_Tip1");
			_lastShownAt = now;
			_lastShownSource = source;
		}
	}
	[HarmonyPatch]
	internal static class WeightUnitPatch
	{
		private static bool _listeningToConfig;

		private static HUDManager? _startedHud;

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate(HUDManager __instance)
		{
			try
			{
				if (_startedHud == __instance || (Object)(object)__instance.weightCounter == (Object)null)
				{
					return;
				}
				PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
				if (!((Object)(object)val == (Object)null))
				{
					_startedHud = __instance;
					if (Plugin.Cfg.WeightInKilograms.Value)
					{
						((TMP_Text)__instance.weightCounter).text = HudText.Weight(val.carryWeight, kilograms: true);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar o peso em kg no início: {arg}");
			}
		}

		[HarmonyPatch(typeof(HUDManager), "UpdateWeightCounter")]
		[HarmonyPostfix]
		private static void AfterUpdateWeightCounter(HUDManager __instance)
		{
			try
			{
				ListenToConfigOnce();
				if (Plugin.Cfg.WeightInKilograms.Value && !((Object)(object)__instance.weightCounter == (Object)null))
				{
					PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
					if (!((Object)(object)val == (Object)null))
					{
						((TMP_Text)__instance.weightCounter).text = HudText.Weight(val.carryWeight, kilograms: true);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar o peso em kg: {arg}");
			}
		}

		private static void ListenToConfigOnce()
		{
			if (_listeningToConfig)
			{
				return;
			}
			_listeningToConfig = true;
			Plugin.Cfg.WeightInKilograms.SettingChanged += delegate
			{
				try
				{
					PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
					if ((Object)(object)HUDManager.Instance != (Object)null && (Object)(object)val != (Object)null)
					{
						HUDManager.Instance.UpdateWeightCounter();
					}
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Falha ao atualizar o peso: {arg}");
				}
			};
		}
	}
}
namespace BitencolaHotFixes.Core
{
	public static class BatteryRecharge
	{
		public static float Next(float charge, float deltaSeconds, float secondsToFull)
		{
			if (secondsToFull <= 0f || float.IsNaN(secondsToFull))
			{
				return 1f;
			}
			if (charge >= 1f)
			{
				return 1f;
			}
			float num = Math.Max(0f, deltaSeconds) / secondsToFull;
			return Math.Min(1f, Math.Max(0f, charge) + num);
		}
	}
	public static class SyncThrottle
	{
		public const float IntervalSeconds = 1f;

		public static bool ShouldSync(float now, float lastSyncAt, bool reachedFull)
		{
			if (!reachedFull)
			{
				return now - lastSyncAt >= 1f;
			}
			return true;
		}
	}
	public readonly struct CorpseCandidate
	{
		public float X { get; }

		public float Y { get; }

		public float Z { get; }

		public bool IsDead { get; }

		public bool IsHidden { get; }

		public CorpseCandidate(float x, float y, float z, bool isDead, bool isHidden)
		{
			X = x;
			Y = y;
			Z = z;
			IsDead = isDead;
			IsHidden = isHidden;
		}
	}
	public static class CorpsePicker
	{
		public const float MaxDistance = 3f;

		public static int PickNearest(IReadOnlyList<CorpseCandidate> candidates, float x, float y, float z, float maxDistance = 3f)
		{
			float num = maxDistance * maxDistance;
			int result = -1;
			float num2 = float.MaxValue;
			for (int i = 0; i < candidates.Count; i++)
			{
				CorpseCandidate corpseCandidate = candidates[i];
				if (corpseCandidate.IsDead && !corpseCandidate.IsHidden)
				{
					float num3 = corpseCandidate.X - x;
					float num4 = corpseCandidate.Y - y;
					float num5 = corpseCandidate.Z - z;
					float num6 = num3 * num3 + num4 * num4 + num5 * num5;
					if (!(num6 > num) && !(num6 >= num2))
					{
						result = i;
						num2 = num6;
					}
				}
			}
			return result;
		}
	}
	public readonly struct ScoreLine
	{
		public string Player { get; }

		public int Kills { get; }

		public int Value { get; }

		public ScoreLine(string player, int kills, int value)
		{
			Player = player;
			Kills = kills;
			Value = value;
		}
	}
	public sealed class DailyScoreboard
	{
		private readonly HashSet<int> _countedBoxes = new HashSet<int>();

		private readonly Dictionary<string, (int kills, int value)> _byPlayer = new Dictionary<string, (int, int)>(StringComparer.Ordinal);

		public bool TryAdd(int boxInstanceId, string player, int value)
		{
			if (!_countedBoxes.Add(boxInstanceId))
			{
				return false;
			}
			_byPlayer.TryGetValue(player, out (int, int) value2);
			_byPlayer[player] = (value2.Item1 + 1, value2.Item2 + value);
			return true;
		}

		public IReadOnlyList<ScoreLine> Lines()
		{
			List<ScoreLine> list = new List<ScoreLine>(_byPlayer.Count);
			foreach (KeyValuePair<string, (int, int)> item in _byPlayer)
			{
				list.Add(new ScoreLine(item.Key, item.Value.Item1, item.Value.Item2));
			}
			list.Sort(delegate(ScoreLine a, ScoreLine b)
			{
				int num = b.Value.CompareTo(a.Value);
				return (num == 0) ? string.CompareOrdinal(a.Player, b.Player) : num;
			});
			return list;
		}

		public string Format()
		{
			IReadOnlyList<ScoreLine> readOnlyList = Lines();
			List<string> list = new List<string>(readOnlyList.Count);
			foreach (ScoreLine item in readOnlyList)
			{
				list.Add(new StringBuilder().Append(item.Player).Append(": ").Append(item.Kills)
					.Append((item.Kills == 1) ? " monstro" : " monstros")
					.Append(", $")
					.Append(item.Value)
					.ToString());
			}
			return TipBody.Join(list);
		}

		public void Exclude(int boxInstanceId)
		{
			_countedBoxes.Add(boxInstanceId);
		}

		public void NewDay()
		{
			_byPlayer.Clear();
		}

		public void Clear()
		{
			_countedBoxes.Clear();
			_byPlayer.Clear();
		}
	}
	public static class DeathCauseText
	{
		private const string Unknown = "causa desconhecida";

		private static readonly string[] Texts = new string[19]
		{
			"causa desconhecida", "espancado", "queda", "explosão", "estrangulado", "sufocado", "dilacerado", "baleado", "esmagado", "afogado",
			"abandonado na lua", "eletrocutado", "chutado", "queimado", "esfaqueado", "ventilador", "inércia", "cortado ao meio", "arranhado"
		};

		public static string For(int causeOfDeath)
		{
			if (causeOfDeath < 0 || causeOfDeath >= Texts.Length)
			{
				return "causa desconhecida";
			}
			return Texts[causeOfDeath];
		}
	}
	public static class DeathNoticeText
	{
		public static (string header, string body) Build(IReadOnlyList<(string name, string cause)> deaths)
		{
			if (deaths.Count == 1)
			{
				return (header: deaths[0].name + " morreu", body: "Causa: " + deaths[0].cause);
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < deaths.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append((i == deaths.Count - 1) ? " e " : ", ");
				}
				stringBuilder.Append(deaths[i].name);
			}
			stringBuilder.Append(" morreram");
			List<string> list = new List<string>(deaths.Count);
			for (int j = 0; j < deaths.Count; j++)
			{
				list.Add(deaths[j].name + ": " + deaths[j].cause);
			}
			return (header: stringBuilder.ToString(), body: TipBody.Join(list));
		}
	}
	public readonly struct PlayerLife
	{
		public int Id { get; }

		public bool Controlled { get; }

		public bool Dead { get; }

		public PlayerLife(int id, bool controlled, bool dead)
		{
			Id = id;
			Controlled = controlled;
			Dead = dead;
		}
	}
	public sealed class DeathWatcher
	{
		private readonly Dictionary<int, bool> _aliveLastTime = new Dictionary<int, bool>();

		public IReadOnlyList<int> Update(IReadOnlyList<PlayerLife> players)
		{
			List<int> list = new List<int>();
			bool value = default(bool);
			for (int i = 0; i < players.Count; i++)
			{
				PlayerLife playerLife = players[i];
				if (playerLife.Controlled && playerLife.Dead && _aliveLastTime.TryGetValue(playerLife.Id, out value) && value)
				{
					list.Add(playerLife.Id);
				}
				_aliveLastTime[playerLife.Id] = playerLife.Controlled && !playerLife.Dead;
			}
			return list;
		}

		public void Reset()
		{
			_aliveLastTime.Clear();
		}
	}
	public static class EmoteRemap
	{
		public static string? NewPath(string actionName, string effectivePath)
		{
			if (Matches(actionName, "Emote1") && Matches(effectivePath, "<Keyboard>/1"))
			{
				return "<Keyboard>/5";
			}
			if (Matches(actionName, "Emote2") && Matches(effectivePath, "<Keyboard>/2"))
			{
				return "<Keyboard>/6";
			}
			return null;
		}

		private static bool Matches(string value, string expected)
		{
			return string.Equals(value, expected, StringComparison.OrdinalIgnoreCase);
		}
	}
	public static class HudText
	{
		private const float PoundsPerWeightUnit = 105f;

		private const double KilogramsPerPound = 0.4536;

		public static string ScrapValue(int value, bool isScrap)
		{
			if (!isScrap || value <= 0)
			{
				return "";
			}
			return "$" + value.ToString(CultureInfo.InvariantCulture);
		}

		public static string Health(int health, int maxHealth = 100)
		{
			return Math.Max(0, health).ToString(CultureInfo.InvariantCulture) + "/" + maxHealth.ToString(CultureInfo.InvariantCulture);
		}

		public static string Weight(float carryWeight, bool kilograms)
		{
			double num = Math.Round(Math.Min(Math.Max(carryWeight - 1f, 0f), 100f) * 105f, MidpointRounding.ToEven);
			if (!kilograms)
			{
				return num.ToString("0", CultureInfo.InvariantCulture) + " lb";
			}
			return Math.Round(num * 0.4536, MidpointRounding.ToEven).ToString("0", CultureInfo.InvariantCulture) + " kg";
		}
	}
	public enum SwitchDecision
	{
		Switch,
		Wait,
		Ignore
	}
	public static class InputGate
	{
		public const float DefaultSwitchCooldown = 0.1f;

		public const float DropCooldown = 0.2f;

		public static SwitchDecision DecideSwitch(in PlayerInputState s, int targetSlot, float cooldown)
		{
			if (!s.CanAct || s.Busy || s.TwoHanded)
			{
				return SwitchDecision.Ignore;
			}
			if (targetSlot < 0 || targetSlot >= s.SlotCount || targetSlot == s.CurrentSlot)
			{
				return SwitchDecision.Ignore;
			}
			if (!(s.TimeSinceSwitch < cooldown))
			{
				return SwitchDecision.Switch;
			}
			return SwitchDecision.Wait;
		}

		public static bool CanDropAll(in PlayerInputState s)
		{
			if (s.CanAct && !s.Busy && s.HasAnyItem)
			{
				return s.TimeSinceSwitch >= 0.2f;
			}
			return false;
		}

		public static bool CanUnloadScrap(in PlayerInputState s, bool inBuildMode)
		{
			if (s.CanAct && !s.Busy && !inBuildMode)
			{
				return s.TimeSinceSwitch >= 0.2f;
			}
			return false;
		}
	}
	public static class ItemWeight
	{
		public static float ToGameWeight(int pounds)
		{
			return 1f + (float)pounds / 105f;
		}
	}
	public sealed class KillCredits
	{
		public const float LateHitWindowSeconds = 0.15f;

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

		private readonly Dictionary<int, float> _diedAt = new Dictionary<int, float>();

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

		public static bool IsLateHitAllowed(bool thisMachineOwnsEnemy, ulong hitterClientId, ulong enemyOwnerClientId)
		{
			if (!thisMachineOwnsEnemy)
			{
				return hitterClientId == enemyOwnerClientId;
			}
			return false;
		}

		public void RecordHit(int enemyInstanceId, string playerName)
		{
			_lastHitBy[enemyInstanceId] = playerName;
		}

		public void RecordHit(int enemyInstanceId, string playerName, bool enemyDead, bool lateHitAllowed, float time)
		{
			float value;
			if (!enemyDead)
			{
				RecordHit(enemyInstanceId, playerName);
			}
			else if (lateHitAllowed && _diedAt.TryGetValue(enemyInstanceId, out value) && time - value <= 0.15f && _lateHitTaken.Add(enemyInstanceId))
			{
				_lastHitBy[enemyInstanceId] = playerName;
			}
		}

		public void MarkDead(int enemyInstanceId, float time)
		{
			_diedAt[enemyInstanceId] = time;
			_lateHitTaken.Remove(enemyInstanceId);
		}

		public string? KillerOf(int enemyInstanceId)
		{
			if (!_lastHitBy.TryGetValue(enemyInstanceId, out string value))
			{
				return null;
			}
			return value;
		}

		public void Clear()
		{
			_lastHitBy.Clear();
			_diedAt.Clear();
			_lateHitTaken.Clear();
		}
	}
	public static class RemainsScanText
	{
		public static string Format(int value, string? killer)
		{
			string text = "Value: $" + value;
			if (killer != null)
			{
				return text + "\nMorto por " + killer;
			}
			return text;
		}
	}
	public enum MonsterTier
	{
		Small,
		Medium,
		Dangerous,
		VeryDangerous,
		Giant,
		Other
	}
	public sealed class TierSpec
	{
		public MonsterTier Tier { get;