Decompiled source of BetterArmory v1.0.8

BetterArmory.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterArmory;
using BetterArmory.Effects;
using BetterArmory.Interactive;
using BetterArmory.Ui;
using Dawn;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Y4NGZInteractions.InteractionAnimationApi;
using Y4NGZUpgrades.HUD;
using Y4NGZUpgrades.Interactive.Feedback;
using Y4NGZUpgrades.Weapons;
using Y4NGZUpgrades.Weapons.Patches;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("ClientNetworkTransform")]
[assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")]
[assembly: IgnoresAccessChecksTo("DissonanceVoip")]
[assembly: IgnoresAccessChecksTo("DunGen")]
[assembly: IgnoresAccessChecksTo("DunGen.Integration.ASPP")]
[assembly: IgnoresAccessChecksTo("DunGen.Integration.UnityNav")]
[assembly: IgnoresAccessChecksTo("EasyTextEffects")]
[assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")]
[assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")]
[assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")]
[assembly: IgnoresAccessChecksTo("Unity.Burst")]
[assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")]
[assembly: IgnoresAccessChecksTo("Unity.Collections")]
[assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")]
[assembly: IgnoresAccessChecksTo("Unity.Jobs")]
[assembly: IgnoresAccessChecksTo("Unity.Mathematics")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")]
[assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")]
[assembly: IgnoresAccessChecksTo("Unity.Services.QoS")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Relay")]
[assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")]
[assembly: IgnoresAccessChecksTo("Unity.Timeline")]
[assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.XR.CoreUtils")]
[assembly: IgnoresAccessChecksTo("Unity.XR.Management")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.ConformanceAutomation")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MetaQuestSupport")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MockRuntime")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.OculusQuestSupport")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.RuntimeDebugger")]
[assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.SpatialTracking")]
[assembly: IgnoresAccessChecksTo("UnityEngine.UI")]
[assembly: IgnoresAccessChecksTo("UnityEngine.XR.LegacyInputHelpers")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BetterArmory")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Better Armory weapon plugin split out of Y4NGZUpgrades")]
[assembly: AssemblyFileVersion("1.0.8.0")]
[assembly: AssemblyInformationalVersion("1.0.8+056cb2a4fc9f440e64713f7de025dc3be65fbe28")]
[assembly: AssemblyProduct("BetterArmory")]
[assembly: AssemblyTitle("BetterArmory")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.8.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 Y4NGZUpgrades.Weapons
{
	internal sealed class AmmoPickupConfig
	{
		public string Id;

		public string DisplayName;

		public WeaponAmmoType AmmoType;

		public string FilePrefix;

		public float TargetWorldSize;

		public int ScrapMinValue;

		public int ScrapMaxValue;

		internal int DefaultRoundsGranted;

		internal int DefaultScrapRarity = 18;

		internal Func<int> ReadRoundsGranted;

		internal Func<int> ReadScrapRarity;

		internal string ShopDescription;

		internal int RoundsGranted
		{
			get
			{
				try
				{
					return Math.Max(0, (ReadRoundsGranted != null) ? ReadRoundsGranted() : DefaultRoundsGranted);
				}
				catch (Exception ex)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[Y4NGZAmmo] Could not read rounds config for " + DisplayName + ": " + ex.Message));
					}
					return Math.Max(0, DefaultRoundsGranted);
				}
			}
		}

		internal int ScrapRarity
		{
			get
			{
				try
				{
					return Math.Max(0, (ReadScrapRarity != null) ? ReadScrapRarity() : DefaultScrapRarity);
				}
				catch (Exception ex)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[Y4NGZAmmo] Could not read scrap rarity for " + DisplayName + ": " + ex.Message));
					}
					return Math.Max(0, DefaultScrapRarity);
				}
			}
		}
	}
	internal static class AmmoPickupCatalog
	{
		internal const string ShotgunShellsBoxId = "shotgun_shells_box";

		internal const string PistolRoundsBoxId = "pistol_rounds_box";

		internal const string HeavyRoundsBoxId = "heavy_rounds_box";

		internal const string FuelTankId = "fuel_tank";

		internal const string GrenadeId = "grenade";

		internal static readonly AmmoPickupConfig ShotgunShellsBox = new AmmoPickupConfig
		{
			Id = "shotgun_shells_box",
			DisplayName = "Shotgun Shells",
			AmmoType = WeaponAmmoType.ShotgunShells,
			FilePrefix = "shotgun_shells",
			TargetWorldSize = 0.28f,
			ScrapMinValue = 8,
			ScrapMaxValue = 16,
			DefaultRoundsGranted = 4,
			DefaultScrapRarity = 18,
			ReadRoundsGranted = () => ArmoryContentConfig.RoundsPerBox(WeaponAmmoType.ShotgunShells, 4),
			ReadScrapRarity = () => ArmoryContentConfig.AmmoInteriorWeight(WeaponAmmoType.ShotgunShells, 18),
			ShopDescription = "Buckshot in a company-issue carton. Feeds the shell reserve every shotgun draws from."
		};

		internal static readonly AmmoPickupConfig PistolRoundsBox = new AmmoPickupConfig
		{
			Id = "pistol_rounds_box",
			DisplayName = "Small Caliber Rounds",
			AmmoType = WeaponAmmoType.SmallCaliberRounds,
			FilePrefix = "pistol_rounds",
			TargetWorldSize = 0.22f,
			ScrapMinValue = 8,
			ScrapMaxValue = 16,
			DefaultRoundsGranted = 6,
			DefaultScrapRarity = 18,
			ReadRoundsGranted = () => ArmoryContentConfig.RoundsPerBox(WeaponAmmoType.SmallCaliberRounds, 6),
			ReadScrapRarity = () => ArmoryContentConfig.AmmoInteriorWeight(WeaponAmmoType.SmallCaliberRounds, 18),
			ShopDescription = "Mixed small-calibre cartridges. Serves the revolver and the pistol from one reserve."
		};

		internal static readonly AmmoPickupConfig HeavyRoundsBox = new AmmoPickupConfig
		{
			Id = "heavy_rounds_box",
			DisplayName = "Rifle Rounds",
			AmmoType = WeaponAmmoType.HeavyCaliberRounds,
			FilePrefix = "heavy_rounds",
			TargetWorldSize = 0.26f,
			ScrapMinValue = 8,
			ScrapMaxValue = 16,
			DefaultRoundsGranted = 10,
			DefaultScrapRarity = 18,
			ReadRoundsGranted = () => ArmoryContentConfig.RoundsPerBox(WeaponAmmoType.HeavyCaliberRounds, 10),
			ReadScrapRarity = () => ArmoryContentConfig.AmmoInteriorWeight(WeaponAmmoType.HeavyCaliberRounds, 18),
			ShopDescription = "Belted rifle brass. The only thing the assault rifle and the bolt-action will eat."
		};

		internal static readonly AmmoPickupConfig FuelTank = new AmmoPickupConfig
		{
			Id = "fuel_tank",
			DisplayName = "Fuel Tank",
			AmmoType = WeaponAmmoType.Fuel,
			FilePrefix = "fuel_tank",
			TargetWorldSize = 0.55f,
			ScrapMinValue = 8,
			ScrapMaxValue = 16,
			DefaultRoundsGranted = 100,
			DefaultScrapRarity = 18,
			ReadRoundsGranted = () => ArmoryContentConfig.RoundsPerBox(WeaponAmmoType.Fuel, 100),
			ReadScrapRarity = () => ArmoryContentConfig.AmmoInteriorWeight(WeaponAmmoType.Fuel, 18),
			ShopDescription = "Pressurised accelerant. Tops the flame thrower's tank back up out in the field."
		};

		internal static readonly AmmoPickupConfig Grenade = new AmmoPickupConfig
		{
			Id = "grenade",
			DisplayName = "Grenade",
			AmmoType = WeaponAmmoType.Grenades,
			FilePrefix = "grenade",
			TargetWorldSize = 0.2f,
			ScrapMinValue = 18,
			ScrapMaxValue = 32,
			DefaultRoundsGranted = 1,
			DefaultScrapRarity = 12,
			ReadRoundsGranted = () => ArmoryContentConfig.RoundsPerBox(WeaponAmmoType.Grenades, 1),
			ReadScrapRarity = () => ArmoryContentConfig.AmmoInteriorWeight(WeaponAmmoType.Grenades, 12),
			ShopDescription = "One fragmentation grenade. Throw it by hand, or load it into the launcher."
		};

		internal static IEnumerable<AmmoPickupConfig> All()
		{
			yield return ShotgunShellsBox;
			yield return PistolRoundsBox;
			yield return HeavyRoundsBox;
			yield return FuelTank;
		}

		internal static AmmoPickupConfig ById(string id)
		{
			foreach (AmmoPickupConfig item in All())
			{
				if (string.Equals(item.Id, id, StringComparison.Ordinal))
				{
					return item;
				}
			}
			return ShotgunShellsBox;
		}
	}
	internal enum AmmoCollectVerdict : byte
	{
		Accepted = 1,
		PickupMissing,
		SenderUnknown,
		SenderNotPlaying,
		AlreadyTaken,
		OutOfReach
	}
	internal struct AmmoCollectFacts
	{
		internal bool PickupFound;

		internal bool SenderFound;

		internal bool SenderAlive;

		internal bool SenderControlled;

		internal bool Held;

		internal bool ClaimedOnServer;

		internal float Distance;

		internal float GrabDistance;
	}
	internal static class AmmoPickupCollectModel
	{
		internal const float VanillaGrabDistance = 5f;

		internal const float ReachSlackMeters = 4f;

		internal const float DebounceSeconds = 0.2f;

		internal const float PendingTimeoutSeconds = 3f;

		internal static AmmoCollectVerdict Judge(AmmoCollectFacts facts)
		{
			if (!facts.PickupFound)
			{
				return AmmoCollectVerdict.PickupMissing;
			}
			if (!facts.SenderFound)
			{
				return AmmoCollectVerdict.SenderUnknown;
			}
			if (!facts.SenderAlive || !facts.SenderControlled)
			{
				return AmmoCollectVerdict.SenderNotPlaying;
			}
			if (facts.Held || facts.ClaimedOnServer)
			{
				return AmmoCollectVerdict.AlreadyTaken;
			}
			if (!(facts.Distance <= ReachLimit(facts.GrabDistance)))
			{
				return AmmoCollectVerdict.OutOfReach;
			}
			return AmmoCollectVerdict.Accepted;
		}

		internal static float ReachLimit(float grabDistance)
		{
			return ((float.IsNaN(grabDistance) || float.IsInfinity(grabDistance)) ? 5f : Math.Max(grabDistance, 5f)) + 4f;
		}

		internal static int RoundsToBank(int granted, float multiplier)
		{
			if (granted <= 0)
			{
				return 0;
			}
			if (!(multiplier > 1f) || float.IsInfinity(multiplier))
			{
				return granted;
			}
			double num = Math.Round((float)granted * multiplier);
			if (!(num >= 2147483647.0))
			{
				return Math.Max(granted, (int)num);
			}
			return int.MaxValue;
		}
	}
	internal enum AmmoCollectBegin
	{
		Started,
		Debounced,
		Busy
	}
	internal sealed class AmmoCollectRequestGate
	{
		private bool _pending;

		private ulong _pendingPickupId;

		private float _pendingSince;

		private float _lastStartedAt = float.NegativeInfinity;

		internal bool HasPending => _pending;

		internal AmmoCollectBegin TryBegin(ulong pickupId, float now, out ulong? expiredPickupId)
		{
			expiredPickupId = null;
			if (now - _lastStartedAt < 0.2f)
			{
				return AmmoCollectBegin.Debounced;
			}
			if (_pending)
			{
				if (now - _pendingSince < 3f)
				{
					return AmmoCollectBegin.Busy;
				}
				expiredPickupId = _pendingPickupId;
			}
			_pending = true;
			_pendingPickupId = pickupId;
			_pendingSince = now;
			_lastStartedAt = now;
			return AmmoCollectBegin.Started;
		}

		internal bool Complete(ulong pickupId)
		{
			if (!_pending || _pendingPickupId != pickupId)
			{
				return false;
			}
			_pending = false;
			return true;
		}

		internal void Clear()
		{
			_pending = false;
			_pendingPickupId = 0uL;
			_pendingSince = 0f;
			_lastStartedAt = float.NegativeInfinity;
		}
	}
	internal sealed class AmmoPickupDevSpawnHost : MonoBehaviour
	{
		internal static bool Enabled = true;

		internal static Key SpawnKey = (Key)70;

		private int _lastSpawnFrame = -1;

		internal static void Ensure()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject host = ArmoryPersistentRunner.Host;
				if (!((Object)(object)host == (Object)null) && !((Object)(object)host.GetComponent<AmmoPickupDevSpawnHost>() != (Object)null))
				{
					host.AddComponent<AmmoPickupDevSpawnHost>();
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)$"[Y4NGZAmmo] Dev spawn host attached (enabled={Enabled}, key={SpawnKey}).");
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Y4NGZAmmo] Could not attach the dev spawn host: " + ex.Message));
				}
			}
		}

		private void Update()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Keyboard current = Keyboard.current;
				if (Enabled && current != null && ((ButtonControl)current[SpawnKey]).wasPressedThisFrame && _lastSpawnFrame != Time.frameCount)
				{
					_lastSpawnFrame = Time.frameCount;
					AmmoPickupRegistry.DebugSpawnAllAtLocalPlayer();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Dev spawn input failed safely: " + ex.Message));
				}
			}
		}
	}
	internal sealed class AmmoPickupItem : MonoBehaviour
	{
		internal const string CollectTooltip = "Collect ammunition";

		public string ConfigId = "shotgun_shells_box";

		[NonSerialized]
		internal bool ConsumptionStarted;

		internal AmmoPickupConfig Config
		{
			get
			{
				try
				{
					return AmmoPickupCatalog.ById(ConfigId);
				}
				catch (Exception ex)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[Y4NGZAmmo] Could not resolve pickup config '" + ConfigId + "': " + ex.Message));
					}
					return AmmoPickupCatalog.ShotgunShellsBox;
				}
			}
		}
	}
	internal static class AmmoPickupNet
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnCollectMessage;

			public static HandleNamedMessageDelegate <1>__OnCollectedMessage;

			public static HandleNamedMessageDelegate <2>__OnSoundMessage;
		}

		private const string CollectMessage = "Y4NGZ_Ammo_Collect_v1";

		private const string CollectedMessage = "Y4NGZ_Ammo_Collected_v1";

		private const string SoundMessage = "Y4NGZ_Ammo_Sound_v1";

		private static readonly AmmoCollectRequestGate Gate = new AmmoCollectRequestGate();

		private static CustomMessagingManager _registeredMessaging;

		internal static void RegisterHandlers()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = (((Object)(object)singleton != (Object)null) ? singleton.CustomMessagingManager : null);
			if (val == null || val == _registeredMessaging)
			{
				return;
			}
			try
			{
				object obj = <>O.<0>__OnCollectMessage;
				if (obj == null)
				{
					HandleNamedMessageDelegate val2 = OnCollectMessage;
					<>O.<0>__OnCollectMessage = val2;
					obj = (object)val2;
				}
				val.RegisterNamedMessageHandler("Y4NGZ_Ammo_Collect_v1", (HandleNamedMessageDelegate)obj);
				object obj2 = <>O.<1>__OnCollectedMessage;
				if (obj2 == null)
				{
					HandleNamedMessageDelegate val3 = OnCollectedMessage;
					<>O.<1>__OnCollectedMessage = val3;
					obj2 = (object)val3;
				}
				val.RegisterNamedMessageHandler("Y4NGZ_Ammo_Collected_v1", (HandleNamedMessageDelegate)obj2);
				object obj3 = <>O.<2>__OnSoundMessage;
				if (obj3 == null)
				{
					HandleNamedMessageDelegate val4 = OnSoundMessage;
					<>O.<2>__OnSoundMessage = val4;
					obj3 = (object)val4;
				}
				val.RegisterNamedMessageHandler("Y4NGZ_Ammo_Sound_v1", (HandleNamedMessageDelegate)obj3);
				_registeredMessaging = val;
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"[Y4NGZAmmo] Ammo collect named-message handlers registered.");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Y4NGZAmmo] Ammo collect handler registration failed: " + ex.Message));
				}
			}
		}

		internal static void Reset()
		{
			_registeredMessaging = null;
			Gate.Clear();
		}

		internal static void RequestCollect(GrabbableObject grabbable)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = (((Object)(object)singleton != (Object)null) ? singleton.CustomMessagingManager : null);
			if (val == null || (Object)(object)grabbable == (Object)null)
			{
				return;
			}
			RegisterHandlers();
			ulong networkObjectId = ((NetworkBehaviour)grabbable).NetworkObjectId;
			if (Gate.TryBegin(networkObjectId, Time.unscaledTime, out var expiredPickupId) != AmmoCollectBegin.Started)
			{
				return;
			}
			if (expiredPickupId.HasValue)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)($"[Y4NGZAmmo] The host never answered the collect request for pickup {expiredPickupId.Value}; " + "a host without this Better Armory version cannot hand out ammo pickups."));
				}
			}
			if (singleton.IsServer)
			{
				HandleCollectOnServer(singleton, singleton.LocalClientId, networkObjectId);
				return;
			}
			FastBufferWriter val2 = default(FastBufferWriter);
			((FastBufferWriter)(ref val2))..ctor(8, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val2)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
				val.SendNamedMessage("Y4NGZ_Ammo_Collect_v1", 0uL, val2, (NetworkDelivery)3);
			}
			catch (Exception ex)
			{
				Gate.Complete(networkObjectId);
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Y4NGZAmmo] Collect request failed to send: " + ex.Message));
				}
			}
			finally
			{
				((FastBufferWriter)(ref val2)).Dispose();
			}
		}

		private static void OnCollectedMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && !singleton.IsServer && senderClientId == 0L)
				{
					ulong pickupId = default(ulong);
					((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref pickupId, default(ForPrimitives));
					byte verdict = default(byte);
					((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref verdict, default(ForPrimitives));
					string configId = default(string);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref configId, false);
					ApplyAnswer(pickupId, (AmmoCollectVerdict)verdict, configId);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[Y4NGZAmmo] Malformed collect answer from {senderClientId}: {ex.Message}");
				}
			}
		}

		private static void ApplyAnswer(ulong pickupId, AmmoCollectVerdict verdict, string configId)
		{
			Gate.Complete(pickupId);
			if (verdict != AmmoCollectVerdict.Accepted)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[Y4NGZAmmo] Host refused collect of pickup {pickupId}: {verdict}.");
				}
				return;
			}
			AmmoPickupConfig ammoPickupConfig = AmmoPickupCatalog.ById(configId);
			int amount = AmmoPickupCollectModel.RoundsToBank(ammoPickupConfig.RoundsGranted, UpgradesBridge.GetAmmoMultiplier());
			if (AmmoReserve.TryAdd(ammoPickupConfig.AmmoType, amount, out var added))
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"[Y4NGZAmmo] Collected {ammoPickupConfig.DisplayName}: +{added} {ammoPickupConfig.AmmoType}.");
				}
			}
			else
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)($"[Y4NGZAmmo] Collected {ammoPickupConfig.DisplayName} but the {ammoPickupConfig.AmmoType} reserve filled " + "while the request was in flight; nothing banked."));
				}
			}
			PlayGrabSound(LocalPlayer(), configId);
		}

		private static void OnCollectMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsServer)
				{
					ulong pickupId = default(ulong);
					((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref pickupId, default(ForPrimitives));
					HandleCollectOnServer(singleton, senderClientId, pickupId);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[Y4NGZAmmo] Malformed collect request from {senderClientId}: {ex.Message}");
				}
			}
		}

		private static void HandleCollectOnServer(NetworkManager manager, ulong senderClientId, ulong pickupId)
		{
			AmmoCollectVerdict ammoCollectVerdict = AmmoCollectVerdict.PickupMissing;
			string configId = string.Empty;
			PlayerControllerB picker = null;
			try
			{
				ammoCollectVerdict = TryClaimAndDespawn(manager, senderClientId, pickupId, out picker, out configId);
			}
			catch (Exception arg)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[Y4NGZAmmo] Collect of pickup {pickupId} for client {senderClientId} failed safely: {arg}");
				}
			}
			if (ammoCollectVerdict == AmmoCollectVerdict.Accepted)
			{
				BroadcastGrabSound(manager, picker, configId, senderClientId);
			}
			if (senderClientId == manager.LocalClientId)
			{
				ApplyAnswer(pickupId, ammoCollectVerdict, configId);
			}
			else
			{
				SendAnswer(manager, senderClientId, pickupId, ammoCollectVerdict, configId);
			}
		}

		private static AmmoCollectVerdict TryClaimAndDespawn(NetworkManager manager, ulong senderClientId, ulong pickupId, out PlayerControllerB picker, out string configId)
		{
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			picker = FindPlayerByClientId(senderClientId);
			configId = string.Empty;
			NetworkObject value = null;
			GrabbableObject val = null;
			AmmoPickupItem ammoPickupItem = null;
			if (manager.SpawnManager != null && manager.SpawnManager.SpawnedObjects.TryGetValue(pickupId, out value) && (Object)(object)value != (Object)null)
			{
				val = ((Component)value).GetComponent<GrabbableObject>();
				ammoPickupItem = ((Component)value).GetComponent<AmmoPickupItem>();
			}
			AmmoCollectFacts facts = new AmmoCollectFacts
			{
				PickupFound = ((Object)(object)val != (Object)null && (Object)(object)ammoPickupItem != (Object)null && value.IsSpawned),
				SenderFound = ((Object)(object)picker != (Object)null),
				SenderAlive = ((Object)(object)picker != (Object)null && !picker.isPlayerDead),
				SenderControlled = ((Object)(object)picker != (Object)null && picker.isPlayerControlled)
			};
			if (facts.PickupFound)
			{
				facts.Held = val.isHeld || val.isPocketed;
				facts.ClaimedOnServer = val.heldByPlayerOnServer || ammoPickupItem.ConsumptionStarted;
			}
			if (facts.PickupFound && (Object)(object)picker != (Object)null)
			{
				Transform val2 = (((Object)(object)picker.gameplayCamera != (Object)null) ? ((Component)picker.gameplayCamera).transform : ((Component)picker).transform);
				facts.Distance = Vector3.Distance(val2.position, ((Component)val).transform.position);
				facts.GrabDistance = picker.grabDistance;
			}
			AmmoCollectVerdict ammoCollectVerdict = AmmoPickupCollectModel.Judge(facts);
			if (ammoCollectVerdict != AmmoCollectVerdict.Accepted)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)($"[Y4NGZAmmo] Refused collect of pickup {pickupId} for client {senderClientId}: {ammoCollectVerdict}" + ((ammoCollectVerdict == AmmoCollectVerdict.OutOfReach) ? $" ({facts.Distance:0.0} m)." : ".")));
				}
				return ammoCollectVerdict;
			}
			val.heldByPlayerOnServer = true;
			ammoPickupItem.ConsumptionStarted = true;
			configId = ammoPickupItem.ConfigId ?? string.Empty;
			string displayName = ammoPickupItem.Config.DisplayName;
			value.Despawn(true);
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)$"[Y4NGZAmmo] Host despawned {displayName} (pickup {pickupId}) for client {senderClientId}.");
			}
			return AmmoCollectVerdict.Accepted;
		}

		private static PlayerControllerB FindPlayerByClientId(ulong clientId)
		{
			PlayerControllerB[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.allPlayerScripts : null);
			if (array == null)
			{
				return null;
			}
			foreach (PlayerControllerB val in array)
			{
				if ((Object)(object)val != (Object)null && val.isPlayerControlled && val.actualClientId == clientId)
				{
					return val;
				}
			}
			return null;
		}

		private static void SendAnswer(NetworkManager manager, ulong clientId, ulong pickupId, AmmoCollectVerdict verdict, string configId)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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_0063: Unknown result type (might be due to invalid IL or missing references)
			CustomMessagingManager customMessagingManager = manager.CustomMessagingManager;
			if (customMessagingManager == null)
			{
				return;
			}
			configId = configId ?? string.Empty;
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(9 + FastBufferWriter.GetWriteSize(configId, false), (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref pickupId, default(ForPrimitives));
				byte b = (byte)verdict;
				((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(configId, false);
				customMessagingManager.SendNamedMessage("Y4NGZ_Ammo_Collected_v1", clientId, val, (NetworkDelivery)3);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[Y4NGZAmmo] Collect answer to client {clientId} failed: {ex.Message}");
				}
			}
			finally
			{
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void BroadcastGrabSound(NetworkManager manager, PlayerControllerB picker, string configId, ulong pickerClientId)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)picker == (Object)null)
			{
				return;
			}
			if (pickerClientId != manager.LocalClientId)
			{
				PlayGrabSound(picker, configId);
			}
			CustomMessagingManager customMessagingManager = manager.CustomMessagingManager;
			if (customMessagingManager == null)
			{
				return;
			}
			configId = configId ?? string.Empty;
			int num = 8 + FastBufferWriter.GetWriteSize(configId, false);
			FastBufferWriter val = default(FastBufferWriter);
			foreach (ulong connectedClientsId in manager.ConnectedClientsIds)
			{
				if (connectedClientsId == pickerClientId || connectedClientsId == manager.LocalClientId)
				{
					continue;
				}
				((FastBufferWriter)(ref val))..ctor(num, (Allocator)2, -1);
				try
				{
					ulong networkObjectId = ((NetworkBehaviour)picker).NetworkObjectId;
					((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
					((FastBufferWriter)(ref val)).WriteValueSafe(configId, false);
					customMessagingManager.SendNamedMessage("Y4NGZ_Ammo_Sound_v1", connectedClientsId, val, (NetworkDelivery)3);
				}
				catch (Exception ex)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)$"[Y4NGZAmmo] Grab sound to client {connectedClientsId} failed: {ex.Message}");
					}
				}
				finally
				{
					((FastBufferWriter)(ref val)).Dispose();
				}
			}
		}

		private static void OnSoundMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || singleton.IsServer || senderClientId != 0L || singleton.SpawnManager == null)
				{
					return;
				}
				ulong key = default(ulong);
				((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref key, default(ForPrimitives));
				string configId = default(string);
				((FastBufferReader)(ref reader)).ReadValueSafe(ref configId, false);
				if (singleton.SpawnManager.SpawnedObjects.TryGetValue(key, out var value) && !((Object)(object)value == (Object)null))
				{
					PlayerControllerB component = ((Component)value).GetComponent<PlayerControllerB>();
					if ((Object)(object)component != (Object)null && (Object)(object)component != (Object)(object)LocalPlayer())
					{
						PlayGrabSound(component, configId);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[Y4NGZAmmo] Malformed grab sound message from {senderClientId}: {ex.Message}");
				}
			}
		}

		private static void PlayGrabSound(PlayerControllerB player, string configId)
		{
			Item val = AmmoPickupRegistry.ItemFor(configId);
			AudioClip val2 = (((Object)(object)val != (Object)null) ? val.grabSFX : null);
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player.itemAudio == (Object)null) && !((Object)(object)val2 == (Object)null))
			{
				player.itemAudio.PlayOneShot(val2, 1f);
			}
		}

		private static PlayerControllerB LocalPlayer()
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if (!((Object)(object)instance != (Object)null))
			{
				return null;
			}
			return instance.localPlayerController;
		}
	}
	internal static class AmmoPickupRegistry
	{
		private readonly struct VisualBuildResult
		{
			internal readonly MeshRenderer Renderer;

			internal readonly Bounds LocalBounds;

			internal VisualBuildResult(MeshRenderer renderer, Bounds localBounds)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				Renderer = renderer;
				LocalBounds = localBounds;
			}
		}

		private const string PickupSoundFileName = "ammo_pickup.mp3";

		private const int PropsLayer = 6;

		private const int ScanNodeFallbackLayer = 22;

		private const float PickupRootGroundOffset = 0.12f;

		private const float VisualFloorClearance = 0.015f;

		private const float MinimumPickupWidth = 0.52f;

		private const float MinimumPickupHeight = 0.24f;

		private const float MinimumPickupDepth = 0.38f;

		private static readonly Dictionary<string, GameObject> Prefabs = new Dictionary<string, GameObject>();

		private static readonly Dictionary<string, Item> Items = new Dictionary<string, Item>();

		private static readonly List<GameObject> PrefabHolders = new List<GameObject>();

		private static bool _registered;

		private static bool _pickupSoundLoadAttempted;

		private static bool _missingPickupSoundWarned;

		private static AudioClip _pickupSound;

		private static Sprite _simpleIcon;

		internal static int RegisteredCount => Prefabs.Count;

		internal static GameObject PrefabFor(string configId)
		{
			try
			{
				GameObject value;
				return Prefabs.TryGetValue(configId ?? string.Empty, out value) ? value : null;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Prefab lookup failed for '" + configId + "': " + ex.Message));
				}
				return null;
			}
		}

		internal static Item ItemFor(string configId)
		{
			try
			{
				Item value;
				return Items.TryGetValue(configId ?? string.Empty, out value) ? value : null;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Item lookup failed for '" + configId + "': " + ex.Message));
				}
				return null;
			}
		}

		internal static bool TryRegisterAll()
		{
			try
			{
				if (_registered)
				{
					ApplySoundsToRegisteredItems();
					TryBeginPickupSoundLoad();
					return true;
				}
				bool flag = true;
				foreach (AmmoPickupConfig item in AmmoPickupCatalog.All())
				{
					if ((Object)(object)PrefabFor(item.Id) != (Object)null && (Object)(object)ItemFor(item.Id) != (Object)null)
					{
						continue;
					}
					try
					{
						BuildAndRegister(item);
					}
					catch (Exception arg)
					{
						flag = false;
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogError((object)$"[Y4NGZAmmo] Registration failed for {item.DisplayName}; other gameplay will continue: {arg}");
						}
					}
				}
				foreach (AmmoPickupConfig item2 in AmmoPickupCatalog.All())
				{
					flag &= (Object)(object)PrefabFor(item2.Id) != (Object)null && (Object)(object)ItemFor(item2.Id) != (Object)null;
				}
				bool num = flag && !_registered;
				_registered = flag;
				if (num)
				{
					if (Prefabs.Count > 0)
					{
						ManualLogSource log2 = Plugin.Log;
						if (log2 != null)
						{
							log2.LogInfo((object)$"[Y4NGZAmmo] Registered {Prefabs.Count} ammo pickups.");
						}
					}
					else
					{
						ManualLogSource log3 = Plugin.Log;
						if (log3 != null)
						{
							log3.LogWarning((object)"[Y4NGZAmmo] Registered 0 ammo pickups: the ammo pickup catalog yielded no entries. No ammunition will spawn or appear in the shop.");
						}
					}
				}
				ApplySoundsToRegisteredItems();
				TryBeginPickupSoundLoad();
				return flag;
			}
			catch (Exception arg2)
			{
				ManualLogSource log4 = Plugin.Log;
				if (log4 != null)
				{
					log4.LogError((object)$"[Y4NGZAmmo] Ammo pickup registration failed safely: {arg2}");
				}
				return false;
			}
		}

		private static void BuildAndRegister(AmmoPickupConfig config)
		{
			GameObject holder = null;
			GameObject val = null;
			Item val2 = null;
			try
			{
				val = BuildItemPrefab(config, out holder);
				val2 = BuildItemProperties(config, val);
				PhysicsProp component = val.GetComponent<PhysicsProp>();
				if ((Object)(object)component == (Object)null)
				{
					throw new InvalidOperationException("The generated prefab has no PhysicsProp component.");
				}
				((GrabbableObject)component).itemProperties = val2;
				val2.spawnPrefab = val;
				DawnLibCompat.FixMixerGroups(val);
				DawnLibCompat.RegisterNetworkPrefab(val);
				WeaponAmmoType family = config.AmmoType;
				DawnLibCompat.RegisterItem(config.Id, val2, () => ArmoryContentConfig.IsAmmoPurchasable(family), () => ArmoryContentConfig.AmmoShopPrice(family), config.ShopDescription, () => config.ScrapRarity);
				Prefabs[config.Id] = val;
				Items[config.Id] = val2;
				if ((Object)(object)holder != (Object)null)
				{
					PrefabHolders.Add(holder);
				}
				NetworkObject component2 = val.GetComponent<NetworkObject>();
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)($"[Y4NGZAmmo] Registered {config.DisplayName} (hash={component2?.GlobalObjectIdHash ?? 0}, " + $"rounds={config.RoundsGranted}, scrapRarity={config.ScrapRarity})."));
				}
			}
			catch
			{
				if ((Object)(object)holder != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)holder);
				}
				else if ((Object)(object)val != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val);
				}
				if ((Object)(object)val2 != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val2);
				}
				throw;
			}
		}

		private static GameObject BuildItemPrefab(AmmoPickupConfig config, out GameObject holder)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			holder = new GameObject("Y4NGZ_" + config.Id + "_InactiveHolder");
			((Object)holder).hideFlags = (HideFlags)61;
			holder.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)holder);
			GameObject val = new GameObject("Y4NGZ_" + config.Id + "_Prefab");
			val.transform.SetParent(holder.transform, false);
			((Object)val).hideFlags = (HideFlags)0;
			val.layer = 6;
			SetTagSafe(val, "PhysicsProp");
			NetworkObject obj = val.AddComponent<NetworkObject>();
			obj.SynchronizeTransform = true;
			obj.SceneMigrationSynchronization = true;
			obj.SpawnWithObservers = true;
			obj.DontDestroyWithOwner = true;
			obj.AutoObjectParentSync = true;
			AssignStableNetworkHash(obj, config.Id);
			Rigidbody val2 = val.AddComponent<Rigidbody>();
			val2.isKinematic = true;
			val2.useGravity = false;
			val2.mass = 0.8f;
			val2.drag = 0.15f;
			val2.angularDrag = 4f;
			AudioSource obj2 = val.AddComponent<AudioSource>();
			obj2.playOnAwake = false;
			obj2.spatialBlend = 1f;
			obj2.dopplerLevel = 0f;
			obj2.rolloffMode = (AudioRolloffMode)0;
			obj2.minDistance = 1f;
			obj2.maxDistance = 16f;
			PhysicsProp val3 = val.AddComponent<PhysicsProp>();
			((GrabbableObject)val3).grabbable = true;
			((GrabbableObject)val3).grabbableToEnemies = false;
			((GrabbableObject)val3).deactivated = false;
			((GrabbableObject)val3).isHeld = false;
			((GrabbableObject)val3).isPocketed = false;
			((GrabbableObject)val3).isInFactory = false;
			((GrabbableObject)val3).scrapValue = (config.ScrapMinValue + config.ScrapMaxValue) / 2;
			((GrabbableObject)val3).rotateObject = false;
			((GrabbableObject)val3).propBody = val2;
			((GrabbableObject)val3).originalScale = Vector3.one;
			((GrabbableObject)val3).customGrabTooltip = "Collect ammunition";
			VisualBuildResult visualBuildResult = BuildVisual(val, config);
			BoxCollider val4 = val.AddComponent<BoxCollider>();
			((Collider)val4).isTrigger = false;
			Bounds localBounds = visualBuildResult.LocalBounds;
			val4.center = ((Bounds)(ref localBounds)).center;
			localBounds = visualBuildResult.LocalBounds;
			float num = Mathf.Max(0.52f, ((Bounds)(ref localBounds)).size.x + 0.05f);
			localBounds = visualBuildResult.LocalBounds;
			float num2 = Mathf.Max(0.24f, ((Bounds)(ref localBounds)).size.y + 0.04f);
			localBounds = visualBuildResult.LocalBounds;
			val4.size = new Vector3(num, num2, Mathf.Max(0.38f, ((Bounds)(ref localBounds)).size.z + 0.05f));
			Vector3 center = val4.center;
			center.y = Mathf.Max(center.y, val4.size.y * 0.5f + 0.015f - 0.12f);
			val4.center = center;
			((Collider)val4).enabled = true;
			((Component)val4).gameObject.layer = 6;
			SetTagSafe(((Component)val4).gameObject, "PhysicsProp");
			((GrabbableObject)val3).propColliders = (Collider[])(object)new Collider[1] { (Collider)val4 };
			((GrabbableObject)val3).mainObjectRenderer = visualBuildResult.Renderer;
			BuildScanNode(val, val4, config);
			val.AddComponent<AmmoPickupItem>().ConfigId = config.Id;
			return val;
		}

		private static VisualBuildResult BuildVisual(GameObject root, AmmoPickupConfig config)
		{
			//IL_046f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0474: Unknown result type (might be due to invalid IL or missing references)
			//IL_047f: Unknown result type (might be due to invalid IL or missing references)
			//IL_048a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0381: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_03be: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0403: Unknown result type (might be due to invalid IL or missing references)
			//IL_040e: Unknown result type (might be due to invalid IL or missing references)
			//IL_041d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Expected O, but got Unknown
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02df: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (config.Id == "grenade" && TryBuildBundledGrenadeVisual(root, config, out var result))
				{
					return result;
				}
				string text = WeaponAssetLoader.AssetDirectory();
				Mesh val = WeaponAssetLoader.LoadMesh(string.IsNullOrWhiteSpace(text) ? null : Path.Combine(text, config.FilePrefix + ".mesh"), config.DisplayName);
				Material val2 = WeaponAssetLoader.BuildMaterial(text, config.FilePrefix, config.DisplayName);
				GameObject val3;
				MeshRenderer val4;
				if (!((Object)(object)val == (Object)null))
				{
					Bounds bounds = val.bounds;
					if (!(WeaponAssetLoader.LongestAxis(((Bounds)(ref bounds)).size) <= 1E-05f))
					{
						val3 = new GameObject("Y4NGZ_" + config.Id + "_Visual");
						val3.transform.SetParent(root.transform, false);
						val3.AddComponent<MeshFilter>().sharedMesh = val;
						val4 = val3.AddComponent<MeshRenderer>();
						goto IL_017c;
					}
				}
				val3 = GameObject.CreatePrimitive((PrimitiveType)3);
				val3.SetActive(false);
				((Object)val3).name = "Y4NGZ_" + config.Id + "_PlaceholderVisual";
				val3.transform.SetParent(root.transform, false);
				Collider component = val3.GetComponent<Collider>();
				if ((Object)(object)component != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)component);
				}
				MeshFilter component2 = val3.GetComponent<MeshFilter>();
				val = ((component2 != null) ? component2.sharedMesh : null);
				val4 = val3.GetComponent<MeshRenderer>();
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] " + config.DisplayName + " is using a primitive-cube placeholder visual."));
				}
				val3.SetActive(true);
				goto IL_017c;
				IL_017c:
				val3.layer = root.layer;
				if ((Object)(object)val4 != (Object)null && (Object)(object)val2 != (Object)null)
				{
					((Renderer)val4).sharedMaterial = val2;
				}
				Bounds val5 = (Bounds)(((Object)(object)val != (Object)null) ? val.bounds : new Bounds(Vector3.zero, Vector3.one));
				float num = Mathf.Max(1E-05f, WeaponAssetLoader.LongestAxis(((Bounds)(ref val5)).size));
				float num2 = Mathf.Max(0.01f, config.TargetWorldSize) / num;
				val3.transform.localRotation = Quaternion.identity;
				val3.transform.localScale = Vector3.one * num2;
				val3.transform.localPosition = new Vector3((0f - ((Bounds)(ref val5)).center.x) * num2, (0f - ((Bounds)(ref val5)).min.y) * num2 + 0.015f - 0.12f, (0f - ((Bounds)(ref val5)).center.z) * num2);
				Vector3 val6 = default(Vector3);
				((Vector3)(ref val6))..ctor(Mathf.Abs(((Bounds)(ref val5)).size.x * num2), Mathf.Abs(((Bounds)(ref val5)).size.y * num2), Mathf.Abs(((Bounds)(ref val5)).size.z * num2));
				Bounds localBounds = default(Bounds);
				((Bounds)(ref localBounds))..ctor(new Vector3(0f, val6.y * 0.5f + 0.015f - 0.12f, 0f), val6);
				return new VisualBuildResult(val4, localBounds);
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Y4NGZAmmo] Visual build failed for " + config.DisplayName + "; using a minimal pickup box: " + ex.Message));
				}
				try
				{
					GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3);
					obj.SetActive(false);
					((Object)obj).name = "Y4NGZ_" + config.Id + "_EmergencyPlaceholderVisual";
					obj.transform.SetParent(root.transform, false);
					Collider component3 = obj.GetComponent<Collider>();
					if ((Object)(object)component3 != (Object)null)
					{
						Object.DestroyImmediate((Object)(object)component3);
					}
					obj.layer = root.layer;
					obj.transform.localPosition = Vector3.up * (config.TargetWorldSize * 0.5f + 0.015f - 0.12f);
					obj.transform.localRotation = Quaternion.identity;
					obj.transform.localScale = Vector3.one * config.TargetWorldSize;
					obj.SetActive(true);
					Bounds localBounds2 = default(Bounds);
					((Bounds)(ref localBounds2))..ctor(new Vector3(0f, config.TargetWorldSize * 0.5f + 0.015f - 0.12f, 0f), Vector3.one * config.TargetWorldSize);
					return new VisualBuildResult(obj.GetComponent<MeshRenderer>(), localBounds2);
				}
				catch (Exception ex2)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogWarning((object)("[Y4NGZAmmo] Emergency placeholder also failed: " + ex2.Message));
					}
					Bounds localBounds3 = default(Bounds);
					((Bounds)(ref localBounds3))..ctor(new Vector3(0f, config.TargetWorldSize * 0.5f + 0.015f - 0.12f, 0f), Vector3.one * config.TargetWorldSize);
					return new VisualBuildResult(null, localBounds3);
				}
			}
		}

		private static bool TryBuildBundledGrenadeVisual(GameObject root, AmmoPickupConfig config, out VisualBuildResult result)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			result = default(VisualBuildResult);
			GameObject val = GrenadeContentAssets.CreateVisual(root.transform);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			((Object)val).name = "Y4NGZ_" + config.Id + "_Visual";
			Renderer[] componentsInChildren = val.GetComponentsInChildren<Renderer>(true);
			if (componentsInChildren.Length == 0)
			{
				Object.DestroyImmediate((Object)(object)val);
				return false;
			}
			Bounds bounds = componentsInChildren[0].bounds;
			for (int i = 1; i < componentsInChildren.Length; i++)
			{
				((Bounds)(ref bounds)).Encapsulate(componentsInChildren[i].bounds);
			}
			float num = Mathf.Max(1E-05f, WeaponAssetLoader.LongestAxis(((Bounds)(ref bounds)).size));
			Transform transform = val.transform;
			transform.localScale *= Mathf.Max(0.01f, config.TargetWorldSize) / num;
			bounds = componentsInChildren[0].bounds;
			for (int j = 1; j < componentsInChildren.Length; j++)
			{
				((Bounds)(ref bounds)).Encapsulate(componentsInChildren[j].bounds);
			}
			Vector3 val2 = root.transform.InverseTransformPoint(((Bounds)(ref bounds)).center);
			Vector3 val3 = root.transform.InverseTransformPoint(new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).min.y, ((Bounds)(ref bounds)).center.z));
			Transform transform2 = val.transform;
			transform2.localPosition += new Vector3(0f - val2.x, -0.105f - val3.y, 0f - val2.z);
			bounds = componentsInChildren[0].bounds;
			for (int k = 1; k < componentsInChildren.Length; k++)
			{
				((Bounds)(ref bounds)).Encapsulate(componentsInChildren[k].bounds);
			}
			Vector3 val4 = root.transform.InverseTransformPoint(((Bounds)(ref bounds)).center);
			Vector3 lossyScale = root.transform.lossyScale;
			Vector3 val5 = default(Vector3);
			((Vector3)(ref val5))..ctor(((Bounds)(ref bounds)).size.x / Mathf.Max(1E-05f, Mathf.Abs(lossyScale.x)), ((Bounds)(ref bounds)).size.y / Mathf.Max(1E-05f, Mathf.Abs(lossyScale.y)), ((Bounds)(ref bounds)).size.z / Mathf.Max(1E-05f, Mathf.Abs(lossyScale.z)));
			Renderer obj = componentsInChildren[0];
			result = new VisualBuildResult((MeshRenderer)(object)((obj is MeshRenderer) ? obj : null), new Bounds(val4, val5));
			return true;
		}

		private static void BuildScanNode(GameObject root, BoxCollider pickupCollider, AmmoPickupConfig config)
		{
			//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_002e: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = new GameObject("ScanNode");
				val.transform.SetParent(root.transform, false);
				val.layer = ResolveLayer("ScanNode", 22);
				val.transform.localPosition = pickupCollider.center + Vector3.up * 0.08f;
				BoxCollider obj = val.AddComponent<BoxCollider>();
				((Collider)obj).isTrigger = true;
				obj.size = pickupCollider.size + Vector3.one * 0.08f;
				ScanNodeProperties obj2 = val.AddComponent<ScanNodeProperties>();
				obj2.headerText = config.DisplayName;
				obj2.subText = "Ammunition";
				obj2.maxRange = 24;
				obj2.minRange = 1;
				obj2.requiresLineOfSight = false;
				obj2.nodeType = 0;
				obj2.creatureScanID = -1;
				obj2.scrapValue = (config.ScrapMinValue + config.ScrapMaxValue) / 2;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Scan node setup failed for " + config.DisplayName + ": " + ex.Message));
				}
			}
		}

		private static Item BuildItemProperties(AmmoPickupConfig config, GameObject prefab)
		{
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Item obj = ScriptableObject.CreateInstance<Item>();
				((Object)obj).name = "Y4NGZ_" + config.Id + "_Item";
				obj.itemName = config.DisplayName;
				obj.spawnPositionTypes = new List<ItemGroup>();
				obj.twoHanded = false;
				obj.twoHandedAnimation = false;
				obj.canBeGrabbedBeforeGameStart = true;
				obj.disallowUtilitySlot = true;
				obj.weight = 1f;
				obj.itemSpawnsOnGround = true;
				obj.isConductiveMetal = true;
				obj.isScrap = true;
				obj.creditsWorth = (config.ScrapMinValue + config.ScrapMaxValue) / 2;
				obj.minValue = config.ScrapMinValue;
				obj.maxValue = config.ScrapMaxValue;
				obj.spawnPrefab = prefab;
				obj.requiresBattery = false;
				obj.itemIcon = WeaponAssetLoader.LoadIconSprite(config.Id) ?? GetSimpleIcon();
				obj.grabAnimationTime = 0.25f;
				obj.syncGrabFunction = true;
				obj.syncUseFunction = false;
				obj.syncDiscardFunction = true;
				obj.syncInteractLRFunction = false;
				obj.saveItemVariable = false;
				obj.toolTips = Array.Empty<string>();
				obj.verticalOffset = 0.12f;
				obj.floorYOffset = 0;
				obj.allowDroppingAheadOfPlayer = true;
				obj.restingRotation = Vector3.zero;
				obj.rotationOffset = Vector3.zero;
				obj.positionOffset = Vector3.zero;
				obj.meshOffset = false;
				obj.canBeInspected = true;
				((Object)obj).hideFlags = (HideFlags)61;
				Object.DontDestroyOnLoad((Object)(object)obj);
				return obj;
			}
			catch (Exception innerException)
			{
				throw new InvalidOperationException("Could not build Item data for " + config.DisplayName + ".", innerException);
			}
		}

		internal static void ApplySoundsToRegisteredItems()
		{
			try
			{
				AudioClip val = WeaponSoundRegistry.FoleyClip(FoleyFamily.Pistol);
				foreach (KeyValuePair<string, Item> item in Items)
				{
					Item value = item.Value;
					if (!((Object)(object)value == (Object)null))
					{
						if ((Object)(object)_pickupSound != (Object)null)
						{
							value.grabSFX = _pickupSound;
						}
						if ((Object)(object)val != (Object)null)
						{
							value.dropSFX = val;
							value.pocketSFX = val;
						}
						GameObject val2 = PrefabFor(item.Key);
						AudioSource val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<AudioSource>() : null);
						WeaponSoundRegistry.ApplyFoleyBusGroup(val3);
						WeaponSoundRegistry.LogFoley("[Y4NGZAmmo]", value.itemName, value, val3);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Could not apply pickup sounds: " + ex.Message));
				}
			}
		}

		private static void TryBeginPickupSoundLoad()
		{
			WeaponSoundRegistry.TryBeginFoleyLoad();
			try
			{
				if (_pickupSoundLoadAttempted || (Object)(object)_pickupSound != (Object)null)
				{
					return;
				}
				string text = WeaponAssetLoader.AssetDirectory();
				string text2 = (string.IsNullOrWhiteSpace(text) ? null : Path.Combine(text, "ammo_pickup.mp3"));
				if (string.IsNullOrWhiteSpace(text2) || !File.Exists(text2))
				{
					_pickupSoundLoadAttempted = true;
					if (!_missingPickupSoundWarned)
					{
						_missingPickupSoundWarned = true;
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogWarning((object)"[Y4NGZAmmo] ammo_pickup.mp3 is missing; ammo pickups will have no grab sound.");
						}
					}
					return;
				}
				_pickupSoundLoadAttempted = true;
				if (ArmoryPersistentRunner.Run(LoadPickupSound(text2)) == null)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)"[Y4NGZAmmo] Could not start the pickup sound load; ammo pickups have no grab sound.");
					}
				}
			}
			catch (Exception ex)
			{
				_pickupSoundLoadAttempted = true;
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[Y4NGZAmmo] Pickup sound load could not start; ammo pickups have no grab sound: " + ex.Message));
				}
			}
		}

		private static IEnumerator LoadPickupSound(string path)
		{
			UnityWebRequest request = null;
			UnityWebRequestAsyncOperation val;
			try
			{
				request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, (AudioType)13);
				val = request.SendWebRequest();
			}
			catch (Exception ex)
			{
				UnityWebRequest obj = request;
				if (obj != null)
				{
					obj.Dispose();
				}
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Pickup sound request failed to start; ammo pickups have no grab sound: " + ex.Message));
				}
				yield break;
			}
			yield return val;
			try
			{
				if ((int)request.result != 1)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[Y4NGZAmmo] Pickup sound failed to load; ammo pickups have no grab sound: " + request.error));
					}
					yield break;
				}
				AudioClip content = DownloadHandlerAudioClip.GetContent(request);
				if ((Object)(object)content == (Object)null)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogWarning((object)"[Y4NGZAmmo] Pickup sound decoded as null; ammo pickups have no grab sound.");
					}
					yield break;
				}
				((Object)content).name = "Y4NGZ_AmmoPickup";
				_pickupSound = content;
				ApplySoundsToRegisteredItems();
				ManualLogSource log4 = Plugin.Log;
				if (log4 != null)
				{
					log4.LogInfo((object)"[Y4NGZAmmo] Loaded shared ammo pickup grab sound.");
				}
			}
			catch (Exception ex2)
			{
				ManualLogSource log5 = Plugin.Log;
				if (log5 != null)
				{
					log5.LogWarning((object)("[Y4NGZAmmo] Pickup sound processing failed; ammo pickups have no grab sound: " + ex2.Message));
				}
			}
			finally
			{
				UnityWebRequest obj2 = request;
				if (obj2 != null)
				{
					obj2.Dispose();
				}
			}
		}

		private static Sprite GetSimpleIcon()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)_simpleIcon != (Object)null)
				{
					return _simpleIcon;
				}
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
				{
					name = "Y4NGZ_AmmoPickupIconTexture",
					hideFlags = (HideFlags)61
				};
				Color val2 = default(Color);
				((Color)(ref val2))..ctor(0.86f, 0.53f, 0.16f, 1f);
				val.SetPixels((Color[])(object)new Color[4] { val2, val2, val2, val2 });
				val.Apply(false, true);
				_simpleIcon = Sprite.Create(val, new Rect(0f, 0f, 2f, 2f), new Vector2(0.5f, 0.5f), 100f);
				((Object)_simpleIcon).name = "Y4NGZ_AmmoPickupIcon";
				((Object)_simpleIcon).hideFlags = (HideFlags)61;
				return _simpleIcon;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Could not create the simple ammo icon: " + ex.Message));
				}
				return null;
			}
		}

		private static void AssignStableNetworkHash(NetworkObject networkObject, string id)
		{
			if ((Object)(object)networkObject == (Object)null)
			{
				return;
			}
			try
			{
				uint num;
				using (MD5 mD = MD5.Create())
				{
					num = BitConverter.ToUInt32(mD.ComputeHash(Encoding.UTF8.GetBytes("com.y4ngz.betterarmory.AmmoPickup." + id)), 0);
				}
				FieldInfo? obj = typeof(NetworkObject).GetField("GlobalObjectIdHash", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(NetworkObject).GetField("<GlobalObjectIdHash>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
				if (obj == null)
				{
					throw new MissingFieldException(typeof(NetworkObject).FullName, "GlobalObjectIdHash");
				}
				obj.SetValue(networkObject, num);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Failed to assign the stable network hash for " + id + ": " + ex.Message));
				}
			}
		}

		private static int ResolveLayer(string name, int fallback)
		{
			try
			{
				int num = LayerMask.NameToLayer(name);
				return (num >= 0) ? num : fallback;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Layer '" + name + "' lookup failed: " + ex.Message));
				}
				return fallback;
			}
		}

		private static void SetTagSafe(GameObject target, string tag)
		{
			try
			{
				if ((Object)(object)target != (Object)null)
				{
					target.tag = tag;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZAmmo] Tag '" + tag + "' was not available for " + ((target != null) ? ((Object)target).name : null) + ": " + ex.Message));
				}
			}
		}

		internal static void DebugSpawnAllAtLocalPlayer()
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!TryRegisterAll())
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)"[Y4NGZAmmo] Dev spawn skipped because ammo pickup registration is incomplete.");
					}
					return;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)"[Y4NGZAmmo] Dev spawn requires being the host.");
					}
					return;
				}
				PlayerControllerB val = StartOfRound.Instance?.localPlayerController;
				if ((Object)(object)val == (Object)null)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogWarning((object)"[Y4NGZAmmo] Dev spawn skipped because the local player is unavailable.");
					}
					return;
				}
				int num = 0;
				foreach (AmmoPickupConfig item in AmmoPickupCatalog.All())
				{
					try
					{
						GameObject val2 = PrefabFor(item.Id);
						if (!((Object)(object)val2 == (Object)null))
						{
							float num2 = ((float)num - 1.5f) * 0.42f;
							Vector3 val3 = ((Component)val).transform.position + ((Component)val).transform.forward * 1.35f + ((Component)val).transform.right * num2 + Vector3.up * 0.55f;
							GameObject val4 = Object.Instantiate<GameObject>(val2, val3, Quaternion.identity);
							val4.SetActive(true);
							NetworkObject component = val4.GetComponent<NetworkObject>();
							if ((Object)(object)component == (Object)null)
							{
								Object.Destroy((Object)(object)val4);
								throw new InvalidOperationException("Spawned prefab has no NetworkObject.");
							}
							component.Spawn(false);
							ManualLogSource log4 = Plugin.Log;
							if (log4 != null)
							{
								log4.LogInfo((object)$"[Y4NGZAmmo] Dev-spawned {item.DisplayName} at {val3}.");
							}
						}
					}
					catch (Exception ex)
					{
						ManualLogSource log5 = Plugin.Log;
						if (log5 != null)
						{
							log5.LogWarning((object)("[Y4NGZAmmo] Dev spawn failed for " + item.DisplayName + ": " + ex.Message));
						}
					}
					finally
					{
						num++;
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log6 = Plugin.Log;
				if (log6 != null)
				{
					log6.LogWarning((object)$"[Y4NGZAmmo] Dev spawn failed safely: {arg}");
				}
			}
		}
	}
	[Serializable]
	internal class AmmoReserveData
	{
		public int shotgunShells;

		public int pistolRounds;

		public int bolts;

		public int fuel;

		public int grenades;

		public bool grantedStartingReserve;

		public int smallCaliberRounds;

		public int heavyRounds;

		public int Get(WeaponAmmoType t)
		{
			return t switch
			{
				WeaponAmmoType.ShotgunShells => shotgunShells, 
				WeaponAmmoType.Fuel => fuel, 
				WeaponAmmoType.Grenades => grenades, 
				WeaponAmmoType.SmallCaliberRounds => smallCaliberRounds, 
				WeaponAmmoType.HeavyCaliberRounds => heavyRounds, 
				_ => 0, 
			};
		}

		public void Set(WeaponAmmoType t, int v)
		{
			v = Mathf.Max(0, v);
			int num = AmmoReserve.MaximumFor(t);
			if (num != int.MaxValue)
			{
				v = Mathf.Min(num, v);
			}
			switch (t)
			{
			case WeaponAmmoType.ShotgunShells:
				shotgunShells = v;
				break;
			case WeaponAmmoType.Fuel:
				fuel = v;
				break;
			case WeaponAmmoType.Grenades:
				grenades = v;
				break;
			case WeaponAmmoType.SmallCaliberRounds:
				smallCaliberRounds = v;
				break;
			case WeaponAmmoType.HeavyCaliberRounds:
				heavyRounds = v;
				break;
			}
		}
	}
	internal static class AmmoReserve
	{
		internal const int MaxGrenades = 2;

		private static AmmoReserveData _data;

		private static string _key;

		private static readonly HashSet<WeaponAmmoType> _devGranted = new HashSet<WeaponAmmoType>();

		private static string SaveDir => Path.Combine(Paths.ConfigPath, "Y4NGZAmmoReserve");

		internal static AmmoReserveData Local => Load();

		internal static int Get(WeaponAmmoType t)
		{
			return Load().Get(t);
		}

		internal static int MaximumFor(WeaponAmmoType t)
		{
			return ArmoryContentConfig.MaxReserve(t);
		}

		internal static void Add(WeaponAmmoType t, int amount)
		{
			TryAdd(t, amount, out var _);
		}

		internal static bool TryAdd(WeaponAmmoType t, int amount, out int added)
		{
			added = 0;
			if (amount <= 0)
			{
				return false;
			}
			AmmoReserveData ammoReserveData = Load();
			int num = ammoReserveData.Get(t);
			int num2 = MaximumFor(t);
			added = ((num2 == int.MaxValue) ? amount : Mathf.Min(amount, Mathf.Max(0, num2 - num)));
			if (added <= 0)
			{
				return false;
			}
			ammoReserveData.Set(t, num + added);
			Save();
			return true;
		}

		internal static int RoomFor(WeaponAmmoType t)
		{
			int num = MaximumFor(t);
			if (num != int.MaxValue)
			{
				return Mathf.Max(0, num - Get(t));
			}
			return int.MaxValue;
		}

		internal static bool TryConsume(WeaponAmmoType t, int amount)
		{
			if (amount <= 0)
			{
				return true;
			}
			AmmoReserveData ammoReserveData = Load();
			if (ammoReserveData.Get(t) < amount)
			{
				return false;
			}
			ammoReserveData.Set(t, ammoReserveData.Get(t) - amount);
			Save();
			return true;
		}

		internal static void Set(WeaponAmmoType t, int value)
		{
			Load().Set(t, value);
			Save();
		}

		internal static void Reload()
		{
			_data = null;
			_key = null;
			_devGranted.Clear();
		}

		internal static bool TryDevGrantOnce(WeaponAmmoType t, int amount)
		{
			if (amount <= 0 || _devGranted.Contains(t) || Get(t) > 0)
			{
				return false;
			}
			_devGranted.Add(t);
			Set(t, amount);
			return true;
		}

		internal static void ResetCurrentSave()
		{
			if (!ArmorySaveBridge.TryGetCurrentExistingOrSlotKey(out var key))
			{
				Reload();
			}
			else
			{
				DeleteForKey(key);
			}
		}

		internal static bool DeleteForKey(string key)
		{
			bool result = false;
			try
			{
				string path = GetPath(key);
				if (File.Exists(path))
				{
					File.Delete(path);
					result = true;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZWeapons] ammo reserve reset failed: " + ex.Message));
				}
			}
			if (string.Equals(_key, key, StringComparison.OrdinalIgnoreCase))
			{
				Reload();
			}
			return result;
		}

		private static AmmoReserveData Load()
		{
			if (!ArmorySaveBridge.TryGetCurrentKey(out var key))
			{
				if (_data == null || _key != null)
				{
					_data = new AmmoReserveData();
					_key = null;
				}
				return _data;
			}
			if (_data != null && _key == key)
			{
				return _data;
			}
			_key = key;
			string path = GetPath(key);
			if (File.Exists(path))
			{
				try
				{
					_data = JsonUtility.FromJson<AmmoReserveData>(File.ReadAllText(path)) ?? new AmmoReserveData();
				}
				catch
				{
					_data = new AmmoReserveData();
				}
			}
			else
			{
				_data = new AmmoReserveData();
			}
			_data.Set(WeaponAmmoType.Grenades, _data.Get(WeaponAmmoType.Grenades));
			if (MigrateRetiredFamilies(_data) | GrantStartingReserveOnce(_data))
			{
				Save();
			}
			return _data;
		}

		private static bool MigrateRetiredFamilies(AmmoReserveData d)
		{
			bool result = false;
			if (d.pistolRounds > 0)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)($"[Y4NGZWeapons] Ammo migration (#148): folding {d.pistolRounds} pistol round(s) " + "into the small-caliber reserve; the revolver now shares that family."));
				}
				d.smallCaliberRounds += d.pistolRounds;
				result = true;
			}
			if (d.pistolRounds != 0)
			{
				d.pistolRounds = 0;
				result = true;
			}
			if (d.bolts != 0)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)($"[Y4NGZWeapons] Ammo migration (#150): dropping {d.bolts} crossbow bolt(s); " + "the crossbow was removed and no weapon fires them."));
				}
				d.bolts = 0;
				result = true;
			}
			return result;
		}

		private static bool GrantStartingReserveOnce(AmmoReserveData d)
		{
			if (d == null || d.grantedStartingReserve)
			{
				return false;
			}
			d.grantedStartingReserve = true;
			List<string> list = new List<string>();
			foreach (AmmoPickupConfig item in AmmoPickupCatalog.All())
			{
				if (item != null)
				{
					int num = ArmoryContentConfig.StartingReserve(item.AmmoType);
					if (num > 0)
					{
						d.Set(item.AmmoType, Math.Max(d.Get(item.AmmoType), num));
						list.Add($"{d.Get(item.AmmoType)} {ArmoryContentConfig.FamilyName(item.AmmoType)}");
					}
				}
			}
			if (list.Count > 0)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[Y4NGZWeapons] Starting ammo reserve granted for this save: " + string.Join(", ", list.ToArray()) + "."));
				}
			}
			return true;
		}

		private static void Save()
		{
			if (_data == null || string.IsNullOrWhiteSpace(_key))
			{
				return;
			}
			try
			{
				if (!Directory.Exists(SaveDir))
				{
					Directory.CreateDirectory(SaveDir);
				}
				File.WriteAllText(GetPath(_key), JsonUtility.ToJson((object)_data, true));
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Y4NGZWeapons] ammo reserve save failed: " + ex.Message));
				}
			}
		}

		private static string GetPath(string key)
		{
			return Path.Combine(SaveDir, Sanitize(key) + ".json");
		}

		private static string Sanitize(string raw)
		{
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			char[] array = raw.ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (Array.IndexOf(invalidFileNameChars, array[i]) >= 0)
				{
					array[i] = '_';
				}
			}
			return new string(array);
		}
	}
	public static class ArmoryAmmoReserveApi
	{
		public const int ProtocolVersion = 1;

		private static readonly (WeaponAmmoType Type, string Label)[] Families = new(WeaponAmmoType, string)[4]
		{
			(WeaponAmmoType.ShotgunShells, "SHOTGUN SHELLS"),
			(WeaponAmmoType.Fuel, "FLAMETHROWER PROPELLANT"),
			(WeaponAmmoType.SmallCaliberRounds, "SMALL CALIBER ROUNDS"),
			(WeaponAmmoType.HeavyCaliberRounds, "RIFLE ROUNDS")
		};

		private static readonly string[] NoIds = new string[0];

		public static string[] GetAmmoFamilyIds()
		{
			try
			{
				List<string> list = new List<string>(Families.Length);
				for (int i = 0; i < Families.Length; i++)
				{
					list.Add(Families[i].Type.ToString());
				}
				return list.ToArray();
			}
			catch (Exception ex)
			{
				Warn("family enumeration", ex);
				return NoIds;
			}
		}

		public static string GetAmmoFamilyLabel(string familyId)
		{
			if (!TryResolve(familyId, out var index))
			{
				return familyId;
			}
			return Families[index].Label;
		}

		public static int GetAmmoFamilyReserve(string familyId)
		{
			if (!TryResolve(familyId, out var index))
			{
				return 0;
			}
			try
			{
				return AmmoReserve.Get(Families[index].Type);
			}
			catch (Exception ex)
			{
				Warn("reserve read", ex);
				return 0;
			}
		}

		public static int GetAmmoFamilyCapacity(string familyId)
		{
			if (!TryResolve(familyId, out var index))
			{
				return 0;
			}
			try
			{
				return AmmoReserve.MaximumFor(Families[index].Type);
			}
			catch (Exception ex)
			{
				Warn("capacity read", ex);
				return 0;
			}
		}

		private static bool TryResolve(string familyId, out int index)
		{
			index = -1;
			if (string.IsNullOrWhiteSpace(familyId))
			{
				return false;
			}
			for (int i = 0; i < Families.Length; i++)
			{
				if (string.Equals(Families[i].Type.ToString(), familyId, StringComparison.Ordinal))
				{
					index = i;
					return true;
				}
			}
			return false;
		}

		private static void Warn(string what, Exception ex)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("[ArmoryAmmoReserveApi] " + what + " failed safely: " + ex.Message));
			}
		}
	}
	internal sealed class WeaponContentSettings
	{
		internal ConfigEntry<bool> Enabled;

		internal ConfigEntry<bool> PurchasableInShop;

		internal ConfigEntry<int> ShopPrice;

		internal ConfigEntry<bool> SpawnsInInteriors;

		internal ConfigEntry<int> InteriorSpawnRarity;

		internal ConfigEntry<int> ScrapValue;

		internal ConfigEntry<int> EnemyDamageNear;

		internal ConfigEntry<int> EnemyDamageFar;

		internal ConfigEntry<int> PlayerDamageNear;

		internal ConfigEntry<int> PlayerDamageFar;

		internal ConfigEntry<int> MagazineSize;

		internal ConfigEntry<float> FireCadenceMultiplier;

		internal ConfigEntry<float> SpreadMultiplier;

		internal ConfigEntry<float> Weight;

		internal ConfigEntry<bool> TwoHanded;

		internal ConfigEntry<bool> TwoHandedAnimation;

		internal ConfigEntry<float> FlameEnemyDamagePerSecond;
	}
	internal sealed class AmmoFamilySettings
	{
		internal ConfigEntry<bool> PurchasableInShop;

		internal ConfigEntry<int> ShopPrice;

		internal ConfigEntry<int> RoundsPerBox;

		internal ConfigEntry<bool> SpawnsInInteriors;

		internal ConfigEntry<int> InteriorSpawnRarity;

		internal ConfigEntry<int> MaxReservePerPlayer;

		internal ConfigEntry<int> StartingReserve;

		internal ConfigEntry<bool> VanillaShotgunSharesReserve;
	}
	internal static class ArmoryContentConfig
	{
		internal const string ShopSection = "Shop";

		internal const int DefaultWeaponInteriorRarity = 8;

		private static readonly Dictionary<string, WeaponContentSettings> Weapons = new Dictionary<string, WeaponContentSettings>(StringComparer.Ordinal);

		private static readonly Dictionary<WeaponAmmoType, AmmoFamilySettings> AmmoFamilies = new Dictionary<WeaponAmmoType, AmmoFamilySettings>();

		private static bool _applied;

		internal static ConfigEntry<float> ShopPriceMultiplier { get; private set; }

		internal static bool VanillaShotgunSharesShellReserve
		{
			get
			{
				AmmoFamilySettings ammoFamilySettings = ForFamily(WeaponAmmoType.ShotgunShells);
				if (ammoFamilySettings?.VanillaShotgunSharesReserve != null)
				{
					return ammoFamilySettings.VanillaShotgunSharesReserve.Value;
				}
				return true;
			}
		}

		internal static string FamilyName(WeaponAmmoType type)
		{
			return type switch
			{
				WeaponAmmoType.ShotgunShells => "Shotgun Shells", 
				WeaponAmmoType.SmallCaliberRounds => "Small Caliber Rounds", 
				WeaponAmmoType.HeavyCaliberRounds => "Rifle Rounds", 
				WeaponAmmoType.Fuel => "Fuel", 
				WeaponAmmoType.Grenades => "Grenades", 
				_ => type.ToString(), 
			};
		}

		private static int DefaultShopPrice(WeaponAmmoType type)
		{
			return type switch
			{
				WeaponAmmoType.ShotgunShells => 20, 
				WeaponAmmoType.SmallCaliberRounds => 20, 
				WeaponAmmoType.HeavyCaliberRounds => 30, 
				WeaponAmmoType.Fuel => 25, 
				WeaponAmmoType.Grenades => 40, 
				_ => 20, 
			};
		}

		private static int DefaultMaxReserve(WeaponAmmoType type)
		{
			if (type != WeaponAmmoType.Grenades)
			{
				return 0;
			}
			return 2;
		}

		internal static string WeaponSection(WeaponConfig config)
		{
			return "Weapon: " + config.DisplayName;
		}

		internal static string AmmoSection(WeaponAmmoType type)
		{
			if (type != WeaponAmmoType.HeavyCaliberRounds)
			{
				return "Ammo: " + FamilyName(type);
			}
			return "Ammo: Heavy Caliber Rounds";
		}

		internal static void Bind(ConfigFile config)
		{
			if (config == null)
			{
				return;
			}
			ShopPriceMultiplier = config.Bind<float>("Shop", "Shop Price Multiplier", 1f, "Multiplier applied to every Y4NGZ weapon and ammunition shop price before it is shown in the terminal. 1.0 leaves each 'Shop Price' exactly as configured. Prices are re-read on each terminal redraw, so this takes effect without a restart. Terminal purchases are host-authoritative, but the printed price comes from the reading player's config - keep this identical across a lobby.");
			Weapons.Clear();
			foreach (WeaponConfig item in WeaponCatalog.AllRegistered())
			{
				BindWeapon(config, item);
			}
			AmmoFamilies.Clear();
			foreach (AmmoPickupConfig item2 in AmmoPickupCatalog.All())
			{
				BindAmmoFamily(config, item2);
			}
		}

		private static void BindWeapon(ConfigFile config, WeaponConfig weapon)
		{
			if (weapon != null && !Weapons.ContainsKey(weapon.Id))
			{
				string text = WeaponSection(weapon);
				WeaponContentSettings weaponContentSettings = new WeaponContentSettings
				{
					Enabled = config.Bind<bool>(text, "Enabled", true, "Register this weapon at all. When false it is never built, never registered as an item, never appears in the shop or in interiors, and is dropped from the LUCKY-8 prize pool. Existing saves holding one will find the item missing, so prefer the shop and interior toggles below for balance changes. Registration is per machine: every player in a lobby must agree."),
					PurchasableInShop = config.Bind<bool>(text, "Purchasable In Shop", true, "List this weapon in the terminal store. ON by default: a buyable armoury is what this mod is for. Turn it off to keep this weapon out of the store without disabling it. Purchases are host-authoritative, but the printed listing comes from the reading player's config."),
					ShopPrice = config.Bind<int>(text, "Shop Price", weapon.CreditsWorth, "Terminal store price in credits, before 'Shop / Shop Price Multiplier'. Re-read on each terminal redraw."),
					SpawnsInInteriors = config.Bind<bool>(text, "Spawns In Interiors", false, "Let this weapon spawn as interior loot. Off by default: weapons have never spawned in interiors and turning one on is a real balance change."),
					InteriorSpawnRarity = config.Bind<int>(text, "Interior Spawn Rarity", 8, "Spawn weight in every moon's scrap pool, used only while 'Spawns In Interiors' is on. Higher is more common; compare against the ammunition families' 18 and vanilla scrap, which runs from about 5 to 100. Zero also removes the weapon from the pool. Weights are recomputed every round, so this needs no restart."),
					ScrapValue = config.Bind<int>(text, "Scrap Value", weapon.CreditsWorth, "Credits this weapon is worth when sold as scrap. Applied when the item is built, so it takes a restart, and it is read on the host when a spawned instance rolls its value."),
					Weight = config.Bind<float>(text, "Weight", weapon.Weight, "Carried weight in Lethal Company's units, where 1.0 is weightless and each 0.01 above it is one pound in the HUD. Applied when the item is built, so it takes a restart."),
					TwoHanded = config.Bind<bool>(text, "Two Handed", weapon.TwoHanded, "Whether carrying this weapon occupies both hands, which blocks the sprint, ladder and second-item interactions a one-handed item allows. Applied when the item is built, so it takes a restart. Evaluated per machine: every player in a lobby must use the same value."),
					TwoHandedAnimation = config.Bind<bool>(text, "Two Handed Animation", weapon.TwoHandedAnimation, "Whether the player's third-person carry pose uses the two-handed hold. Applied when the item is built, so it takes a restart, and every player in a lobby must use the same value. CAUTION: this is a pose, not a rule - setting it against what the weapon's authored hold was built for makes the model sit wrong in other players' hands even though it looks correct in your own.")
				};
				if (weapon.Native)
				{
					weaponContentSettings.EnemyDamageNear = config.Bind<int>(text, "Enemy Damage Near", weapon.EnemyDamageClose, "Damage dealt to CREATURES at point-blank range, falling off linearly to 'Enemy Damage Far' at the weapon's maximum range. This is Lethal Company's own enemy damage scale, NOT player health: most creatures have between 1 and 10 hit points (a Bracken has 5, a Hoarding Bug 3, a Thumper 6) and a shovel hits for 1, so 1 to 3 is the normal range for a weapon here and small changes are large balance changes. On a weapon that fires a pattern this is PER PELLET - the Heavy Shotgun's ten pellets each apply this value at their own distance. Zero means the weapon does not damage creatures at all. HOST-AUTHORITATIVE: enemy damage is resolved on the server, so the host's value is the one that lands.");
					weaponContentSettings.EnemyDamageFar = config.Bind<int>(text, "Enemy Damage Far", weapon.EnemyDamageFar, "Damage dealt to CREATURES at maximum range, on the same 1-to-10 enemy scale as 'Enemy Damage Near' and per pellet in the same way. HOST-AUTHORITATIVE.");
					weaponContentSettings.PlayerDamageNear = config.Bind<int>(text, "Player Damage Near", weapon.PlayerDamageClose, "Friendly-fire damage dealt to PLAYERS at point-blank range, falling off linearly to 'Player Damage Far' at the weapon's maximum range. This is player health, where a player has 100 - so it is a completely different scale from the 'Enemy Damage' pair above and must not be tuned against it. Applied once per shot rather than per pellet. Resolved on the victim's own machine.");
					weaponContentSettings.PlayerDamageFar = config.Bind<int>(text, "Player Damage Far", weapon.PlayerDamageFar, "Friendly-fire damage dealt to PLAYERS at maximum range, out of 100 player health. Same scale as 'Player Damage Near'.");
					weaponContentSettings.MagazineSize = config.Bind<int>(text, "Magazine Size", weapon.MagazineSize, "Rounds the weapon holds when full. Read locally by every player's reload, HUD and save/load path, so this MUST MATCH across a lobby - a mismatch makes two players disagree about when the gun is empty.");
					weaponContentSettings.FireCadenceMultiplier = config.Bind<float>(text, "Fire Cadence Multiplier", 1f, "Multiplier on the weapon's rate of fire. 2.0 fires twice as fast, 0.5 half as fast. Applied to the authored rounds-per-minute (or, on a weapon that has none, to its shot cooldown). Evaluated locally: keep it identical across a lobby.");
					weaponContentSettings.SpreadMultiplier = config.Bind<float>(text, "Spread Multiplier", 1f, "Multiplier on the weapon's base spread cone, before the movement, stance, shot and recoil bloom terms. Below 1 tightens the pattern. The crosshair is sized from the same number, so it follows automatically. Pellets are resolved on the shooter's machine.");
				}
				else if (weapon.Id == "grenade_launcher")
				{
					weaponContentSettings.MagazineSize = config.Bind<int>(text, "Magazine Size", weapon.MagazineSize, "Grenades the launcher holds when full. Read locally by the reload path, so this MUST MATCH across a lobby.");
				}
				if (weapon.Id == "flame_thrower")
				{
					weaponContentSettings.FlameEnemyDamagePerSecond = config.Bind<float>(text, "Enemy Damage Per Second", 0.5f, "Damage per second the flame thrower deals to a CREATURE it is touching, on Lethal Company's enemy scale (a shovel hits for 1; most creatures have between 1 and 10 hit points). The stream and its lingering ground fire share this ONE rate - a creature standing in both still takes it once, not twice - and the burning effect set on a creature is visual only. Applied as 1 force every (1 / rate) seconds: the shipped 0.5 is 1 every 2 s, so a 5 hit-point creature dies after about 8 s in the flame. Rates under 0.1 clamp to 0.1; 0 disables enemy damage. HOST-AUTHORITATIVE: the stream is resolved on the server.");
				}
				Weapons[weapon.Id] = weaponContentSettings;
			}
		}

		private static void BindAmmoFamily(ConfigFile config, AmmoPickupConfig pickup)
		{
			if (pickup != null && !AmmoFamilies.ContainsKey(pickup.AmmoType))
			{
				string text = AmmoSection(pickup.AmmoType);
				AmmoFamilySettings ammoFamilySettings = new AmmoFamilySettings
				{
					PurchasableInShop = config.Bind<bool>(text, "Purchasable In Shop", true, "List " + pickup.DisplayName + " in the terminal store. ON by default: buyable weapons are only usable alongside buyable ammunition. Interior spawning stays on as well, so ammunition is both found and bought."),
					ShopPrice = config.Bind<int>(text, "Shop Price", DefaultShopPrice(pickup.AmmoType), "Terminal store price in credits for one " + pickup.DisplayName + " pickup, before 'Shop / Shop Price Multiplier'. One purchase delivers one pickup, which grants 'Rounds Per Box' rounds when collected."),
					RoundsPerBox = config.Bind<int>(text, "Rounds Per Box", pickup.DefaultRoundsGranted, "Reserve rounds granted when one " + pickup.DisplayName + " pickup is collected. Each player consumes their own pickup locally."),
					SpawnsInInteriors = config.Bind<bool>(text, "Spawns In Interiors", true, "Let " + pickup.DisplayName + " spawn as interior loot. On by default - this is how ammunition has always been distributed."),
					InteriorSpawnRarity = config.Bind<int>(text, "Interior Spawn Rarity", pickup.DefaultScrapRarity, "Spawn weight in every moon's scrap pool, used only while 'Spawns In Interiors' is on. Higher is more common. Zero also removes the pickup from the pool. Weights are recomputed every round, so this needs no restart."),
					MaxReservePerPlayer = config.Bind<int>(text, "Max Reserve Per Player", DefaultMaxReserve(pickup.AmmoType), "Hard cap on how much of this family one player may carry in reserve. 0 means unlimited. Enforced on pickup, on purchase delivery and on the reserve file itself, so lowering it clamps an over-full reserve on the next save. The reserve is per player and per save, so this is a local rule, not a host one."),
					StartingReserve = config.Bind<int>(text, "Starting Reserve", 0, "Reserve rounds of this family granted ONCE per save file, the first time that save's reserve is loaded. Raising it later does not re-grant; a save that has already been granted is marked as such in its reserve file. Clamped by 'Max Reserve Per Player'.")
				};
				if (pickup.AmmoType == WeaponAmmoType.ShotgunShells)
				{
					ammoFamilySettings.VanillaShotgunSharesReserve = config.Bind<bool>(text, "Vanilla Shotgun Shares Shell Reserve", true, "Let Lethal Company's own shotgun reload from this reserve once it has run out of hotbar ammo. Hotbar shells always take priority; this is only the fallback. Turn it off to keep the shell reserve exclusively for Y4NGZ weapons.");
				}
				AmmoFamilies[pickup.AmmoType] = ammoFamilySettings;
			}
		}

		internal static void ApplyToCatalogs()
		{
			if (_applied)
			{
				return;
			}
			_applied = true;
			foreach (WeaponConfig item in WeaponCatalog.AllRegistered())
			{
				if (item == null || !Weapons.TryGetValue(item.Id, out var value))
				{
					continue;
				}
				try
				{
					if (value.ScrapValue != null)
					{
						item.CreditsWorth = Mathf.Max(0, value.ScrapValue.Value);
					}
					if (value.EnemyDamageNear != null)
					{
						item.EnemyDamageClose = Mathf.Max(0, value.EnemyDamageNear.Value);
					}
					if (value.EnemyDamageFar != null)
					{
						item.EnemyDamageFar = Mathf.Max(0, value.EnemyDamageFar.Value);
					}
					if (value.PlayerDamageNear != null)
					{
						item.PlayerDamageClose = Mathf.Max(0, value.PlayerDamageNear.Value);
					}
					if (value.PlayerDamageFar != null)
					{
						item.PlayerDamageFar = Mathf.Max(0, value.PlayerDamageFar.Value);
					}
					if (value.FlameEnemyDamagePerSecond != null)
					{