Decompiled source of GunGame Progressions v1.3.6

GunGameProgressionsMetadataExporter.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using FistVR;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("GunGameProgressionsMetadataExporter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.6.0")]
[assembly: AssemblyInformationalVersion("1.3.6")]
[assembly: AssemblyProduct("GunGameProgressionsMetadataExporter")]
[assembly: AssemblyTitle("GunGameProgressionsMetadataExporter")]
[assembly: AssemblyVersion("1.3.6.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HLin.GunGameProgressions
{
	public sealed class EnemySpawnWeight
	{
		public int Value { get; private set; }

		public int Multiplicity { get; private set; }

		public EnemySpawnWeight(int value, int multiplicity)
		{
			Value = value;
			Multiplicity = multiplicity;
		}
	}
	public static class EnemyWeightPolicy
	{
		private enum OperatorTier
		{
			None,
			Standard,
			Advanced,
			Apex
		}

		public static EnemySpawnWeight Resolve(RuntimeEnemyEntry enemy)
		{
			if (enemy == null)
			{
				throw new ArgumentNullException("enemy");
			}
			int value = EnemyValue(enemy);
			return new EnemySpawnWeight(value, SpawnMultiplicity(enemy, value));
		}

		private static int EnemyValue(RuntimeEnemyEntry enemy)
		{
			switch (GetOperatorTier(enemy.EnemyNameString))
			{
			case OperatorTier.Standard:
				return 2;
			case OperatorTier.Advanced:
			case OperatorTier.Apex:
				return 1;
			default:
				switch (enemy.EnemyNameString)
				{
				case "RW_Rot":
					return 8;
				case "M_Swat_Scout":
					return 5;
				case "M_MercWiener_Riflewiener":
					return 3;
				case "M_Swat_SpecOps":
					return 2;
				case "M_Swat_Heavy":
					return 1;
				default:
					if (!IsCoreEnemyFamily(enemy.EnemyNameString))
					{
						return OtherSpawnWeight(enemy.DifficultyScore);
					}
					return CoreSpawnWeight(enemy.DifficultyScore);
				}
			}
		}

		private static int CoreSpawnWeight(int score)
		{
			if (score <= 15)
			{
				return 8;
			}
			if (score <= 40)
			{
				return 5;
			}
			if (score <= 65)
			{
				return 3;
			}
			if (score <= 100)
			{
				return 2;
			}
			return 1;
		}

		private static int OtherSpawnWeight(int score)
		{
			if (score > 40)
			{
				return 1;
			}
			return 2;
		}

		private static int SpawnMultiplicity(RuntimeEnemyEntry enemy, int value)
		{
			switch (GetOperatorTier(enemy.EnemyNameString))
			{
			case OperatorTier.Apex:
				return 1;
			case OperatorTier.Standard:
			case OperatorTier.Advanced:
				return 2;
			default:
				if (!IsCoreEnemyFamily(enemy.EnemyNameString))
				{
					return 1;
				}
				return value switch
				{
					8 => 13, 
					5 => 8, 
					3 => 6, 
					2 => 4, 
					_ => 2, 
				};
			}
		}

		private static bool IsCoreEnemyFamily(string enemyNameString)
		{
			if (!enemyNameString.StartsWith("RW_", StringComparison.Ordinal) && !enemyNameString.StartsWith("M_Swat_", StringComparison.Ordinal) && !enemyNameString.StartsWith("M_MercWiener_", StringComparison.Ordinal))
			{
				return enemyNameString.StartsWith("Comperator_", StringComparison.Ordinal);
			}
			return true;
		}

		private static OperatorTier GetOperatorTier(string enemyNameString)
		{
			if (string.IsNullOrEmpty(enemyNameString) || !enemyNameString.StartsWith("Comperator_", StringComparison.OrdinalIgnoreCase))
			{
				return OperatorTier.None;
			}
			if (ContainsToken(enemyNameString, "Heavy_") || ContainsToken(enemyNameString, "Tier5") || ContainsToken(enemyNameString, "MixedHighTier"))
			{
				return OperatorTier.Apex;
			}
			if (ContainsToken(enemyNameString, "Tier4") || ContainsToken(enemyNameString, "MixedMedTier") || ContainsToken(enemyNameString, "Medium_Tier3"))
			{
				return OperatorTier.Advanced;
			}
			return OperatorTier.Standard;
		}

		private static bool ContainsToken(string value, string token)
		{
			return value.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0;
		}
	}
	public sealed class MountResolution
	{
		public string RawMount { get; private set; }

		public string CanonicalMount { get; private set; }

		public bool IsResolved { get; private set; }

		private MountResolution(string rawMount, string canonicalMount, bool isResolved)
		{
			RawMount = rawMount;
			CanonicalMount = canonicalMount;
			IsResolved = isResolved;
		}

		public static MountResolution Resolve(string rawMount)
		{
			if (string.IsNullOrEmpty(rawMount) || rawMount.Trim().Length == 0)
			{
				return new MountResolution(string.Empty, string.Empty, isResolved: false);
			}
			string text = rawMount.Trim();
			if (string.Equals(text, "99", StringComparison.Ordinal))
			{
				return new MountResolution(text, "RMR", isResolved: true);
			}
			if (int.TryParse(text, out var _))
			{
				return new MountResolution(text, string.Empty, isResolved: false);
			}
			return new MountResolution(text, text, isResolved: true);
		}
	}
	public static class PipScopeOpticClassifier
	{
		public static string Classify(string objectId, bool hasPipScope, bool hasReflexSight)
		{
			if (!string.IsNullOrEmpty(objectId) && objectId.IndexOf("magnifier", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return "Magnifier";
			}
			if (hasPipScope)
			{
				return "Scope";
			}
			if (!hasReflexSight)
			{
				return string.Empty;
			}
			return "Reflex";
		}
	}
	[BepInPlugin("HLin.GunGameProgressionsMetadataExporter", "GunGame Progressions Metadata Exporter", "1.3.6")]
	[BepInProcess("h3vr.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class RuntimeFirearmInspection
		{
			public string Category { get; private set; }

			public int? MagazineType { get; private set; }

			public List<string> PhysicalMountTypes { get; private set; }

			public RuntimeFirearmInspection(string category, int? magazineType, List<string> physicalMountTypes)
			{
				Category = category;
				MagazineType = magazineType;
				PhysicalMountTypes = physicalMountTypes;
			}
		}

		private const int StableSamplesRequired = 3;

		private const int MaximumSamples = 120;

		private const int MetadataEntriesPerFrame = 64;

		private void Start()
		{
			((MonoBehaviour)this).StartCoroutine(ExportWhenObjectDataIsStable());
		}

		private IEnumerator ExportWhenObjectDataIsStable()
		{
			RuntimeMetadataStabilityGate stabilityGate = new RuntimeMetadataStabilityGate(3);
			for (int sample = 0; sample < 120; sample++)
			{
				if (!TryGetObjectData(out var objects) || objects.Count == 0)
				{
					yield return (object)new WaitForSeconds(1f);
					continue;
				}
				if (stabilityGate.Observe(objects.Count))
				{
					yield return ((MonoBehaviour)this).StartCoroutine(WriteRuntimeMetadata(objects, "final-refresh"));
					yield break;
				}
				yield return (object)new WaitForSeconds(1f);
			}
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Timed out waiting for late H3VR object metadata growth; packaged vanilla fallback profiles remain available for this launch.");
		}

		private bool TryGetObjectData(out Dictionary<string, FVRObject> objects)
		{
			objects = null;
			try
			{
				objects = IM.OD;
				return objects != null;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("H3VR object data is not ready: " + ex.Message));
				return false;
			}
		}

		private unsafe IEnumerator WriteRuntimeMetadata(Dictionary<string, FVRObject> objects, string phase)
		{
			List<RuntimeMetadataEntry> entries = new List<RuntimeMetadataEntry>();
			int skipped = 0;
			List<FVRObject> snapshot = objects.Values.Where((FVRObject item) => (Object)(object)item != (Object)null).ToList();
			for (int index = 0; index < snapshot.Count; index++)
			{
				FVRObject val = snapshot[index];
				if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(val.ItemID))
				{
					skipped++;
					continue;
				}
				string declaredCategory = ((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref val.Category)/*cast due to .constrained prefix*/).ToString();
				string opticKind = GetOpticKind(val);
				RuntimeFirearmInspection runtimeFirearmInspection = InspectFirearmPrefab(val, declaredCategory);
				string category = runtimeFirearmInspection.Category;
				entries.Add(new RuntimeMetadataEntry
				{
					ObjectID = val.ItemID,
					Category = category,
					IsModContent = val.IsModContent,
					MagazineType = (runtimeFirearmInspection.MagazineType ?? ((int)val.MagazineType)),
					ClipType = (int)val.ClipType,
					RoundType = (int)val.RoundType,
					CompatibleMagazines = ObjectIds(val.CompatibleMagazines),
					CompatibleClips = ObjectIds(val.CompatibleClips),
					CompatibleSpeedLoaders = ObjectIds(val.CompatibleSpeedLoaders),
					CompatibleSingleRounds = ObjectIds(val.CompatibleSingleRounds),
					BespokeAttachments = ObjectIds(val.BespokeAttachments),
					FirearmSize = ((object)Unsafe.As<OTagFirearmSize, OTagFirearmSize>(ref val.TagFirearmSize)/*cast due to .constrained prefix*/).ToString(),
					FirearmRoundPower = ((object)Unsafe.As<OTagFirearmRoundPower, OTagFirearmRoundPower>(ref val.TagFirearmRoundPower)/*cast due to .constrained prefix*/).ToString(),
					FirearmMounts = ((val.TagFirearmMounts == null) ? new List<string>() : val.TagFirearmMounts.Select((OTagFirearmMount mount) => ((object)(*(OTagFirearmMount*)(&mount))/*cast due to .constrained prefix*/).ToString()).ToList()),
					AttachmentMount = ((object)Unsafe.As<OTagFirearmMount, OTagFirearmMount>(ref val.TagAttachmentMount)/*cast due to .constrained prefix*/).ToString(),
					AttachmentFeature = ((object)Unsafe.As<OTagAttachmentFeature, OTagAttachmentFeature>(ref val.TagAttachmentFeature)/*cast due to .constrained prefix*/).ToString(),
					OpticKind = opticKind,
					PhysicalMountTypes = (runtimeFirearmInspection.PhysicalMountTypes ?? GetPhysicalMountTypes(val, category, opticKind))
				});
				if (index > 0 && index % 64 == 0)
				{
					yield return null;
				}
			}
			entries.Sort((RuntimeMetadataEntry left, RuntimeMetadataEntry right) => string.CompareOrdinal(left.ObjectID, right.ObjectID));
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			string text = Path.Combine(directoryName, "ObjectData.json");
			WriteTextAtomically(text, SerializeMetadata(entries));
			ProfileRules rules;
			try
			{
				rules = ProfileRules.Load(directoryName);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Unable to load GunGame profile rules: " + ex.Message));
				yield break;
			}
			List<RuntimeMetadataEntry> sourceEntries = entries.Where((RuntimeMetadataEntry entry) => !rules.IsBlacklisted(entry)).ToList();
			int hashCode = Guid.NewGuid().GetHashCode();
			List<RuntimeEnemyEntry> list = BuildEnemyEntries();
			RuntimeGenerationResult runtimeGenerationResult = RuntimeProfileBuilder.BuildWithDiagnostics(sourceEntries, list, new Random(hashCode));
			string text2 = Path.Combine(directoryName, "RuntimePools");
			Directory.CreateDirectory(text2);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (RuntimeWeaponPool pool in runtimeGenerationResult.Pools)
			{
				string text3 = RuntimePoolFileName(pool);
				hashSet.Add(text3);
				WriteTextAtomically(Path.Combine(directoryName, text3), SerializePool(pool));
			}
			RemoveStaleRuntimePools(directoryName, hashSet);
			RemoveStaleRuntimePools(text2, new HashSet<string>(StringComparer.Ordinal));
			WriteTextAtomically(Path.Combine(text2, "runtime-generation-receipt.json"), SerializeReceipt(entries, list, runtimeGenerationResult, hashCode, phase));
			WriteTextAtomically(Path.Combine(text2, "enemy-catalog.json"), SerializeEnemyCatalog(list));
			int num = entries.Count((RuntimeMetadataEntry entry) => entry.IsModContent);
			int num2 = ((runtimeGenerationResult.Pools.Count != 0) ? runtimeGenerationResult.Pools[0].Guns.Count : 0);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Exported " + entries.Count + " active metadata entries (" + num + " modded) to " + text + "; generated " + runtimeGenerationResult.Pools.Count + " advanced runtime GunGame pools with " + num2 + " firearms and " + list.Count + " active Sosig types during " + phase + "; skipped " + runtimeGenerationResult.SkippedFirearms.Count + " without a compatible feed."));
		}

		private static List<string> ObjectIds(IEnumerable<FVRObject> objects)
		{
			if (objects == null)
			{
				return new List<string>();
			}
			return (from item in objects
				where (Object)(object)item != (Object)null && !string.IsNullOrEmpty(item.ItemID)
				select item.ItemID).Distinct().OrderBy<string, string>((string item) => item, StringComparer.Ordinal).ToList();
		}

		private static RuntimeFirearmInspection InspectFirearmPrefab(FVRObject item, string declaredCategory)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Expected I4, but got Unknown
			if (declaredCategory != "Firearm")
			{
				return new RuntimeFirearmInspection(declaredCategory, null, null);
			}
			try
			{
				GameObject gameObject = ((AnvilAsset)item).GetGameObject();
				if ((Object)(object)gameObject == (Object)null)
				{
					return new RuntimeFirearmInspection(declaredCategory, null, null);
				}
				FVRPhysicalObject[] componentsInChildren = gameObject.GetComponentsInChildren<FVRPhysicalObject>(true);
				FVRFireArm val = null;
				bool hasMagazine = false;
				bool hasClip = false;
				bool hasSpeedloader = false;
				bool hasRound = false;
				FVRPhysicalObject[] array = componentsInChildren;
				foreach (FVRPhysicalObject val2 in array)
				{
					if (val2 is FVRFireArm)
					{
						val = (FVRFireArm)(object)((val2 is FVRFireArm) ? val2 : null);
					}
					else if (val2 is FVRFireArmMagazine)
					{
						hasMagazine = true;
					}
					else if (val2 is FVRFireArmClip)
					{
						hasClip = true;
					}
					else if (val2 is Speedloader)
					{
						hasSpeedloader = true;
					}
					else if (val2 is FVRFireArmRound)
					{
						hasRound = true;
					}
				}
				string category = RuntimeItemRole.Resolve(declaredCategory, (Object)(object)val != (Object)null, hasMagazine, hasClip, hasSpeedloader, hasRound);
				int? magazineType = (((Object)(object)val == (Object)null) ? ((int?)null) : new int?((int)val.MagazineType));
				List<string> physicalMountTypes = (((Object)(object)val == (Object)null) ? null : GetPhysicalMountTypes(gameObject));
				return new RuntimeFirearmInspection(category, magazineType, physicalMountTypes);
			}
			catch (Exception)
			{
				return new RuntimeFirearmInspection(declaredCategory, null, null);
			}
		}

		private static List<string> GetPhysicalMountTypes(FVRObject item, string category, string opticKind)
		{
			string text = string.Empty;
			if (category == "Firearm")
			{
				text = "FistVR.FVRFireArmAttachmentMount";
			}
			else if (category == "Attachment" && !string.IsNullOrEmpty(opticKind))
			{
				text = "FistVR.FVRFireArmAttachment";
			}
			if (string.IsNullOrEmpty(text))
			{
				return new List<string>();
			}
			try
			{
				GameObject gameObject = ((AnvilAsset)item).GetGameObject();
				if ((Object)(object)gameObject == (Object)null)
				{
					return new List<string>();
				}
				return GetPhysicalMountTypes(gameObject, text);
			}
			catch (Exception)
			{
				return new List<string>();
			}
		}

		private static List<string> GetPhysicalMountTypes(GameObject prefab)
		{
			return GetPhysicalMountTypes(prefab, "FistVR.FVRFireArmAttachmentMount");
		}

		private static List<string> GetPhysicalMountTypes(GameObject prefab, string componentTypeName)
		{
			Type type = typeof(FVRObject).Assembly.GetType(componentTypeName, throwOnError: false);
			if ((object)type == null)
			{
				return new List<string>();
			}
			FieldInfo typeField = type.GetField("Type", BindingFlags.Instance | BindingFlags.Public);
			if ((object)typeField == null)
			{
				return new List<string>();
			}
			return (from value in (from component in prefab.GetComponentsInChildren(type, true)
					select typeField.GetValue(component) into value
					where value != null
					select value).Select(ResolveRuntimeMountName)
				where !string.IsNullOrEmpty(value)
				select value).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string value) => value, StringComparer.Ordinal).ToList();
		}

		private static string ResolveRuntimeMountName(object value)
		{
			Type type = value.GetType();
			if (!type.IsEnum || !Enum.IsDefined(type, value))
			{
				return value.ToString();
			}
			return Enum.GetName(type, value) ?? value.ToString();
		}

		private static List<RuntimeEnemyEntry> BuildEnemyEntries()
		{
			//IL_0050: 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_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			List<RuntimeEnemyEntry> list = new List<RuntimeEnemyEntry>();
			IM instance = ManagerSingleton<IM>.Instance;
			if ((Object)(object)instance == (Object)null || instance.odicSosigObjsByID == null)
			{
				return list;
			}
			foreach (KeyValuePair<SosigEnemyID, SosigEnemyTemplate> item in instance.odicSosigObjsByID)
			{
				SosigEnemyTemplate value = item.Value;
				if (!((Object)(object)value == (Object)null) && (int)item.Key != 0)
				{
					bool flag = Enum.IsDefined(typeof(SosigEnemyID), item.Key);
					string text = (flag ? ((object)item.Key/*cast due to .constrained prefix*/).ToString() : Convert.ToInt32(item.Key).ToString());
					int num = EnemyHealthScore(value);
					int num2 = EnemyArmorScore(value);
					int num3 = EnemyWeaponThreatScore(value);
					int num4 = EnemySpecialThreatScore(value);
					list.Add(new RuntimeEnemyEntry
					{
						EnemyNameString = text,
						DisplayName = (string.IsNullOrEmpty(value.DisplayName) ? text : value.DisplayName),
						IsModContent = !flag,
						IsSpawnable = true,
						HealthScore = num,
						ArmorScore = num2,
						WeaponThreatScore = num3,
						SpecialThreatScore = num4,
						DifficultyScore = Math.Max(1, num + num2 + num3 + num4)
					});
				}
			}
			return list.OrderBy((RuntimeEnemyEntry entry) => entry.DifficultyScore).ThenBy<RuntimeEnemyEntry, string>((RuntimeEnemyEntry entry) => entry.EnemyNameString, StringComparer.Ordinal).ToList();
		}

		private static int EnemyHealthScore(SosigEnemyTemplate template)
		{
			List<SosigConfigTemplate> list = EnemyConfigs(template).ToList();
			if (list.Count == 0)
			{
				return 1;
			}
			return Math.Max(1, (int)Math.Round(list.Average((SosigConfigTemplate config) => config.TotalMustard) / 25f));
		}

		private static int EnemyArmorScore(SosigEnemyTemplate template)
		{
			float num = 0f;
			foreach (SosigOutfitConfig item in template.OutfitConfig ?? new List<SosigOutfitConfig>())
			{
				num += OutfitSlotScore(item.Headwear, item.Chance_Headwear);
				num += OutfitSlotScore(item.Eyewear, item.Chance_Eyewear) * 0.5f;
				num += OutfitSlotScore(item.Facewear, item.Chance_Facewear) * 0.75f;
				num += OutfitSlotScore(item.Torsowear, item.Chance_Torsowear) * 1.5f;
				num += OutfitSlotScore(item.Pantswear, item.Chance_Pantswear) * 0.5f;
			}
			return (int)Math.Round(num * 3f);
		}

		private static float OutfitSlotScore(List<FVRObject> items, float chance)
		{
			if (items == null || items.Count == 0)
			{
				return 0f;
			}
			if (!(chance > 0f))
			{
				return 1f;
			}
			return chance;
		}

		private static int EnemyWeaponThreatScore(SosigEnemyTemplate template)
		{
			List<FVRObject> list = new List<FVRObject>();
			list.AddRange(template.WeaponOptions ?? new List<FVRObject>());
			list.AddRange(template.WeaponOptions_Secondary ?? new List<FVRObject>());
			list.AddRange(template.WeaponOptions_Tertiary ?? new List<FVRObject>());
			int num = list.Where((FVRObject weapon) => (Object)(object)weapon != (Object)null).Select(WeaponThreatScore).DefaultIfEmpty(0)
				.Max();
			int num2 = Math.Min(10, list.Count((FVRObject weapon) => (Object)(object)weapon != (Object)null));
			return num + num2 + (int)Math.Round((template.SecondaryChance + template.TertiaryChance) * 4f);
		}

		private static int WeaponThreatScore(FVRObject weapon)
		{
			if (((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref weapon.Category)/*cast due to .constrained prefix*/).ToString() != "Firearm")
			{
				return 1;
			}
			return ((object)Unsafe.As<OTagFirearmRoundPower, OTagFirearmRoundPower>(ref weapon.TagFirearmRoundPower)/*cast due to .constrained prefix*/).ToString() switch
			{
				"Tiny" => 2, 
				"Pistol" => 4, 
				"Shotgun" => 6, 
				"Intermediate" => 7, 
				"FullPower" => 9, 
				"AntiMaterial" => 12, 
				"Ordnance" => 14, 
				"Exotic" => 12, 
				_ => 5, 
			};
		}

		private static int EnemySpecialThreatScore(SosigEnemyTemplate template)
		{
			float num = 0f;
			foreach (SosigConfigTemplate item in EnemyConfigs(template))
			{
				num = Math.Max(num, Math.Max(0f, item.RunSpeed - 3f) * 2f);
				num = Math.Max(num, Math.Max(0f, item.ViewDistance - 150f) / 100f);
				num = Math.Max(num, HasBooleanProperty(item, "HasNightVision") ? 4f : 0f);
				num = Math.Max(num, item.AppliesDamageResistToIntegrityLoss ? 3f : 0f);
				num = Math.Max(num, (!item.CanBeGrabbed || !item.CanBeSevered || !item.CanBeStabbed) ? 3f : 0f);
			}
			return (int)Math.Ceiling(num);
		}

		private static IEnumerable<SosigConfigTemplate> EnemyConfigs(SosigEnemyTemplate template)
		{
			return from config in (template.ConfigTemplates ?? new List<SosigConfigTemplate>()).Concat(template.ConfigTemplates_Easy ?? new List<SosigConfigTemplate>())
				where (Object)(object)config != (Object)null
				select config;
		}

		private static bool HasBooleanProperty(object value, string propertyName)
		{
			PropertyInfo property = value.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
			if ((object)property != null && (object)property.PropertyType == typeof(bool))
			{
				return (bool)property.GetValue(value, null);
			}
			return false;
		}

		private static string GetOpticKind(FVRObject item)
		{
			if (((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref item.Category)/*cast due to .constrained prefix*/).ToString() != "Attachment")
			{
				return string.Empty;
			}
			string text = ((object)Unsafe.As<OTagAttachmentFeature, OTagAttachmentFeature>(ref item.TagAttachmentFeature)/*cast due to .constrained prefix*/).ToString();
			if (text != "Reflex" && text != "Magnification")
			{
				return string.Empty;
			}
			try
			{
				GameObject gameObject = ((AnvilAsset)item).GetGameObject();
				if ((Object)(object)gameObject == (Object)null)
				{
					return string.Empty;
				}
				Component component = GetComponent(gameObject, "FistVR.PIPScopeController");
				Component component2 = GetComponent(gameObject, "FistVR.ReflexSightController");
				return PipScopeOpticClassifier.Classify(item.ItemID, (Object)(object)component != (Object)null, (Object)(object)component2 != (Object)null);
			}
			catch (Exception)
			{
			}
			return string.Empty;
		}

		private static Component GetComponent(GameObject prefab, string typeName)
		{
			Type type = typeof(FVRObject).Assembly.GetType(typeName, throwOnError: false);
			if ((object)type != null)
			{
				return prefab.GetComponentInChildren(type, true);
			}
			return null;
		}

		private static string RuntimePoolFileName(RuntimeWeaponPool pool)
		{
			return "GunGameWeaponPool_Runtime_" + pool.Family + "_" + pool.EnemyType + ".json";
		}

		private static void RemoveStaleRuntimePools(string runtimePoolsPath, HashSet<string> expectedPoolFiles)
		{
			string[] files = Directory.GetFiles(runtimePoolsPath, "GunGameWeaponPool_Runtime_*.json");
			foreach (string path in files)
			{
				if (!expectedPoolFiles.Contains(Path.GetFileName(path)))
				{
					File.Delete(path);
				}
			}
		}

		private static void WriteTextAtomically(string outputPath, string contents)
		{
			string text = outputPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
			File.WriteAllText(text, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			try
			{
				if (File.Exists(outputPath))
				{
					File.Replace(text, outputPath, null);
				}
				else
				{
					File.Move(text, outputPath);
				}
			}
			catch (PlatformNotSupportedException)
			{
				File.Copy(text, outputPath, overwrite: true);
				File.Delete(text);
			}
		}

		private static string SerializeMetadata(List<RuntimeMetadataEntry> entries)
		{
			StringBuilder stringBuilder = new StringBuilder(entries.Count * 256);
			stringBuilder.Append("[\n");
			for (int i = 0; i < entries.Count; i++)
			{
				RuntimeMetadataEntry runtimeMetadataEntry = entries[i];
				stringBuilder.Append("  {\"ObjectID\":\"");
				AppendJsonString(stringBuilder, runtimeMetadataEntry.ObjectID);
				stringBuilder.Append("\",\"Category\":\"");
				AppendJsonString(stringBuilder, runtimeMetadataEntry.Category);
				stringBuilder.Append("\",\"IsModContent\":");
				stringBuilder.Append(runtimeMetadataEntry.IsModContent ? "true" : "false");
				stringBuilder.Append(",\"MagazineType\":");
				stringBuilder.Append(runtimeMetadataEntry.MagazineType);
				stringBuilder.Append(",\"ClipType\":");
				stringBuilder.Append(runtimeMetadataEntry.ClipType);
				stringBuilder.Append(",\"RoundType\":");
				stringBuilder.Append(runtimeMetadataEntry.RoundType);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleMagazines", runtimeMetadataEntry.CompatibleMagazines);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleClips", runtimeMetadataEntry.CompatibleClips);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleSpeedLoaders", runtimeMetadataEntry.CompatibleSpeedLoaders);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleSingleRounds", runtimeMetadataEntry.CompatibleSingleRounds);
				AppendJsonNamedStringArray(stringBuilder, "BespokeAttachments", runtimeMetadataEntry.BespokeAttachments);
				AppendJsonNamedString(stringBuilder, "FirearmSize", runtimeMetadataEntry.FirearmSize);
				AppendJsonNamedString(stringBuilder, "FirearmRoundPower", runtimeMetadataEntry.FirearmRoundPower);
				AppendJsonNamedStringArray(stringBuilder, "FirearmMounts", runtimeMetadataEntry.FirearmMounts);
				AppendJsonNamedString(stringBuilder, "AttachmentMount", runtimeMetadataEntry.AttachmentMount);
				AppendJsonNamedString(stringBuilder, "AttachmentFeature", runtimeMetadataEntry.AttachmentFeature);
				AppendJsonNamedString(stringBuilder, "OpticKind", runtimeMetadataEntry.OpticKind);
				AppendJsonNamedStringArray(stringBuilder, "PhysicalMountTypes", runtimeMetadataEntry.PhysicalMountTypes);
				stringBuilder.Append('}');
				if (i < entries.Count - 1)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append('\n');
			}
			stringBuilder.Append(']');
			return stringBuilder.ToString();
		}

		private static string SerializePool(RuntimeWeaponPool pool)
		{
			StringBuilder stringBuilder = new StringBuilder(pool.Guns.Count * 128);
			stringBuilder.Append("{\n  \"WeaponPoolType\": \"Advanced\",\n  \"Description\": \"");
			AppendJsonString(stringBuilder, pool.Description);
			stringBuilder.Append("\",\n  \"EnemyProgressionType\": ");
			stringBuilder.Append(pool.EnemyProgressionType);
			stringBuilder.Append(",\n  \"Enemies\": [");
			for (int i = 0; i < pool.Enemies.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append("{\"EnemyName\":0,\"EnemyNameString\":\"");
				AppendJsonString(stringBuilder, pool.Enemies[i].EnemyNameString);
				stringBuilder.Append("\",\"Value\":");
				stringBuilder.Append(pool.Enemies[i].Value);
				stringBuilder.Append('}');
			}
			stringBuilder.Append("],\n  \"Guns\": [");
			for (int j = 0; j < pool.Guns.Count; j++)
			{
				if (j > 0)
				{
					stringBuilder.Append(',');
				}
				RuntimeGun runtimeGun = pool.Guns[j];
				stringBuilder.Append("{\"GunName\":\"");
				AppendJsonString(stringBuilder, runtimeGun.GunName);
				stringBuilder.Append("\",\"MagName\":\"");
				AppendJsonString(stringBuilder, runtimeGun.MagName);
				stringBuilder.Append("\",\"MagNames\":");
				AppendJsonStringArray(stringBuilder, runtimeGun.MagNames);
				stringBuilder.Append(",\"CategoryID\":");
				stringBuilder.Append(runtimeGun.CategoryID);
				stringBuilder.Append(",\"Extra\":\"");
				AppendJsonString(stringBuilder, runtimeGun.Extra);
				stringBuilder.Append("\"}");
			}
			stringBuilder.Append("],\n  \"Name\": \"");
			AppendJsonString(stringBuilder, pool.Name);
			stringBuilder.Append("\",\n  \"OrderType\": ");
			stringBuilder.Append(pool.OrderType);
			stringBuilder.Append("\n}");
			return stringBuilder.ToString();
		}

		private static string SerializeReceipt(List<RuntimeMetadataEntry> entries, List<RuntimeEnemyEntry> enemies, RuntimeGenerationResult result, int randomSeed, string phase)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{\n  \"generatedAtUtc\": \"");
			AppendJsonString(stringBuilder, DateTime.UtcNow.ToString("o"));
			stringBuilder.Append("\",\n  \"randomSeed\": ");
			stringBuilder.Append(randomSeed);
			stringBuilder.Append(",\n  \"phase\": \"");
			AppendJsonString(stringBuilder, phase);
			stringBuilder.Append('"');
			stringBuilder.Append(",\n  \"activeItems\": ");
			stringBuilder.Append(entries.Count);
			stringBuilder.Append(",\n  \"activeModdedItems\": ");
			stringBuilder.Append(entries.Count((RuntimeMetadataEntry entry) => entry.IsModContent));
			stringBuilder.Append(",\n  \"activeSosigTypes\": ");
			stringBuilder.Append(enemies.Count);
			stringBuilder.Append(",\n  \"activeModdedSosigTypes\": ");
			stringBuilder.Append(enemies.Count((RuntimeEnemyEntry enemy) => enemy.IsModContent));
			stringBuilder.Append(",\n  \"eligibleWeaponsPerPool\": ");
			stringBuilder.Append((result.Pools.Count != 0) ? result.Pools[0].Guns.Count : 0);
			stringBuilder.Append(",\n  \"skippedFirearms\": ");
			AppendJsonStringArray(stringBuilder, result.SkippedFirearms);
			stringBuilder.Append(",\n  \"firearmsWithoutOptics\": ");
			AppendJsonStringArray(stringBuilder, result.FirearmsWithoutOptics);
			stringBuilder.Append("\n}");
			return stringBuilder.ToString();
		}

		private static string SerializeEnemyCatalog(List<RuntimeEnemyEntry> enemies)
		{
			StringBuilder stringBuilder = new StringBuilder(enemies.Count * 192);
			stringBuilder.Append("[\n");
			for (int i = 0; i < enemies.Count; i++)
			{
				RuntimeEnemyEntry runtimeEnemyEntry = enemies[i];
				stringBuilder.Append("  {\"EnemyNameString\":\"");
				AppendJsonString(stringBuilder, runtimeEnemyEntry.EnemyNameString);
				stringBuilder.Append("\",\"DisplayName\":\"");
				AppendJsonString(stringBuilder, runtimeEnemyEntry.DisplayName);
				stringBuilder.Append("\",\"IsModContent\":");
				stringBuilder.Append(runtimeEnemyEntry.IsModContent ? "true" : "false");
				stringBuilder.Append(",\"IsSpawnable\":");
				stringBuilder.Append(runtimeEnemyEntry.IsSpawnable ? "true" : "false");
				stringBuilder.Append(",\"DifficultyScore\":");
				stringBuilder.Append(runtimeEnemyEntry.DifficultyScore);
				stringBuilder.Append(",\"HealthScore\":");
				stringBuilder.Append(runtimeEnemyEntry.HealthScore);
				stringBuilder.Append(",\"ArmorScore\":");
				stringBuilder.Append(runtimeEnemyEntry.ArmorScore);
				stringBuilder.Append(",\"WeaponThreatScore\":");
				stringBuilder.Append(runtimeEnemyEntry.WeaponThreatScore);
				stringBuilder.Append(",\"SpecialThreatScore\":");
				stringBuilder.Append(runtimeEnemyEntry.SpecialThreatScore);
				stringBuilder.Append('}');
				if (i < enemies.Count - 1)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append('\n');
			}
			stringBuilder.Append(']');
			return stringBuilder.ToString();
		}

		private static void AppendJsonNamedString(StringBuilder json, string name, string value)
		{
			json.Append(",\"");
			AppendJsonString(json, name);
			json.Append("\":\"");
			AppendJsonString(json, value ?? string.Empty);
			json.Append('"');
		}

		private static void AppendJsonNamedStringArray(StringBuilder json, string name, List<string> values)
		{
			json.Append(",\"");
			AppendJsonString(json, name);
			json.Append("\":");
			AppendJsonStringArray(json, values);
		}

		private static void AppendJsonStringArray(StringBuilder json, List<string> values)
		{
			values = values ?? new List<string>();
			json.Append('[');
			for (int i = 0; i < values.Count; i++)
			{
				if (i > 0)
				{
					json.Append(',');
				}
				json.Append('"');
				AppendJsonString(json, values[i]);
				json.Append('"');
			}
			json.Append(']');
		}

		private static void AppendJsonString(StringBuilder json, string value)
		{
			string text = value ?? string.Empty;
			foreach (char c in text)
			{
				switch (c)
				{
				case '\\':
					json.Append("\\\\");
					continue;
				case '"':
					json.Append("\\\"");
					continue;
				case '\n':
					json.Append("\\n");
					continue;
				case '\r':
					json.Append("\\r");
					continue;
				case '\t':
					json.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					json.Append("\\u");
					int num = c;
					json.Append(num.ToString("x4"));
				}
				else
				{
					json.Append(c);
				}
			}
		}
	}
	public sealed class ProfileRules
	{
		public string[] FirearmBlacklist { get; set; }

		public string[] FeedBlacklist { get; set; }

		public static ProfileRules Load(string packageDirectory)
		{
			string text = Path.Combine(packageDirectory, "profile-rules.json");
			if (!File.Exists(text))
			{
				throw new FileNotFoundException("GunGame profile rules are missing.", text);
			}
			string json = File.ReadAllText(text);
			return new ProfileRules
			{
				FirearmBlacklist = ReadStringArray(json, "firearmBlacklist"),
				FeedBlacklist = ReadStringArray(json, "feedBlacklist")
			};
		}

		public bool IsBlacklisted(RuntimeMetadataEntry entry)
		{
			if (!(entry.Category == "Firearm"))
			{
				return Contains(FeedBlacklist, entry.ObjectID);
			}
			return Contains(FirearmBlacklist, entry.ObjectID);
		}

		private static bool Contains(IEnumerable<string> values, string value)
		{
			foreach (string value2 in values)
			{
				if (string.Equals(value2, value, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static string[] ReadStringArray(string json, string propertyName)
		{
			string pattern = "\\\"" + Regex.Escape(propertyName) + "\\\"\\s*:\\s*\\[(?<items>.*?)\\]";
			Match match = Regex.Match(json, pattern, RegexOptions.Singleline);
			if (!match.Success)
			{
				return new string[0];
			}
			List<string> list = new List<string>();
			foreach (Match item in Regex.Matches(match.Groups["items"].Value, "\\\"(?<value>(?:\\\\.|[^\\\"\\\\])*)\\\""))
			{
				list.Add(item.Groups["value"].Value.Replace("\\\\\"", "\"").Replace("\\\\\\\\", "\\"));
			}
			return list.ToArray();
		}
	}
	public static class RuntimeItemRole
	{
		public static string Resolve(string declaredCategory, bool hasFirearm, bool hasMagazine, bool hasClip, bool hasSpeedloader, bool hasRound)
		{
			if (declaredCategory != "Firearm")
			{
				return declaredCategory;
			}
			if (hasFirearm)
			{
				return "Firearm";
			}
			if (hasMagazine)
			{
				return "Magazine";
			}
			if (hasClip)
			{
				return "Clip";
			}
			if (hasSpeedloader)
			{
				return "SpeedLoader";
			}
			if (hasRound)
			{
				return "Cartridge";
			}
			return "Unknown";
		}
	}
	public sealed class RuntimeMetadataStabilityGate
	{
		private readonly int requiredStableSamples;

		private int initialCount = -1;

		private int previousCount = -1;

		private int stableSamples;

		private bool observedLateGrowth;

		public RuntimeMetadataStabilityGate(int requiredStableSamples)
		{
			if (requiredStableSamples < 1)
			{
				throw new ArgumentOutOfRangeException("requiredStableSamples");
			}
			this.requiredStableSamples = requiredStableSamples;
		}

		public bool Observe(int itemCount)
		{
			if (itemCount <= 0)
			{
				return false;
			}
			if (initialCount < 0)
			{
				initialCount = itemCount;
			}
			if (itemCount != previousCount)
			{
				observedLateGrowth = observedLateGrowth || itemCount > initialCount;
				previousCount = itemCount;
				stableSamples = 0;
				return false;
			}
			stableSamples++;
			if (observedLateGrowth)
			{
				return stableSamples >= requiredStableSamples;
			}
			return false;
		}
	}
	public sealed class RuntimeMetadataEntry
	{
		public string ObjectID { get; set; }

		public string Category { get; set; }

		public bool IsModContent { get; set; }

		public int MagazineType { get; set; }

		public int ClipType { get; set; }

		public int RoundType { get; set; }

		public List<string> CompatibleMagazines { get; set; }

		public List<string> CompatibleClips { get; set; }

		public List<string> CompatibleSpeedLoaders { get; set; }

		public List<string> CompatibleSingleRounds { get; set; }

		public List<string> BespokeAttachments { get; set; }

		public string FirearmSize { get; set; }

		public string FirearmRoundPower { get; set; }

		public List<string> FirearmMounts { get; set; }

		public string AttachmentMount { get; set; }

		public string AttachmentFeature { get; set; }

		public string OpticKind { get; set; }

		public List<string> PhysicalMountTypes { get; set; }
	}
	public sealed class RuntimeEnemyEntry
	{
		public string EnemyNameString { get; set; }

		public string DisplayName { get; set; }

		public bool IsModContent { get; set; }

		public bool IsSpawnable { get; set; }

		public int DifficultyScore { get; set; }

		public int HealthScore { get; set; }

		public int ArmorScore { get; set; }

		public int WeaponThreatScore { get; set; }

		public int SpecialThreatScore { get; set; }
	}
	public sealed class RuntimeGun
	{
		public string GunName { get; set; }

		public string MagName { get; set; }

		public List<string> MagNames { get; set; }

		public string Extra { get; set; }

		public int CategoryID { get; set; }
	}
	public sealed class RuntimeEnemy
	{
		public string EnemyNameString { get; set; }

		public int Value { get; set; }
	}
	public sealed class RuntimeWeaponPool
	{
		public string Name { get; set; }

		public string Description { get; set; }

		public int OrderType { get; set; }

		public string WeaponPoolType { get; set; }

		public int EnemyProgressionType { get; set; }

		public string Family { get; set; }

		public string EnemyType { get; set; }

		public List<RuntimeEnemy> Enemies { get; set; }

		public List<RuntimeGun> Guns { get; set; }
	}
	public sealed class RuntimeGenerationResult
	{
		public List<RuntimeWeaponPool> Pools { get; set; }

		public List<string> SkippedFirearms { get; set; }

		public List<string> FirearmsWithoutOptics { get; set; }
	}
	public static class RuntimeProfileBuilder
	{
		private sealed class FeedCandidate
		{
			public string ObjectID { get; private set; }

			public int CategoryID { get; private set; }

			public FeedCandidate(string objectId, int categoryId)
			{
				ObjectID = objectId;
				CategoryID = categoryId;
			}
		}

		public static List<RuntimeWeaponPool> Build(IEnumerable<RuntimeMetadataEntry> sourceEntries, Random random)
		{
			return BuildWithDiagnostics(sourceEntries, DefaultEnemies(), random).Pools;
		}

		public static List<RuntimeWeaponPool> Build(IEnumerable<RuntimeMetadataEntry> sourceEntries, IEnumerable<RuntimeEnemyEntry> sourceEnemies, Random random)
		{
			return BuildWithDiagnostics(sourceEntries, sourceEnemies, random).Pools;
		}

		public static RuntimeGenerationResult BuildWithDiagnostics(IEnumerable<RuntimeMetadataEntry> sourceEntries, Random random)
		{
			return BuildWithDiagnostics(sourceEntries, DefaultEnemies(), random);
		}

		public static RuntimeGenerationResult BuildWithDiagnostics(IEnumerable<RuntimeMetadataEntry> sourceEntries, IEnumerable<RuntimeEnemyEntry> sourceEnemies, Random random)
		{
			if (sourceEntries == null)
			{
				throw new ArgumentNullException("sourceEntries");
			}
			if (random == null)
			{
				throw new ArgumentNullException("random");
			}
			if (sourceEnemies == null)
			{
				throw new ArgumentNullException("sourceEnemies");
			}
			List<RuntimeMetadataEntry> list = sourceEntries.Where((RuntimeMetadataEntry entry) => entry != null && !string.IsNullOrEmpty(entry.ObjectID)).OrderBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToList();
			Dictionary<string, RuntimeMetadataEntry> entriesById = list.GroupBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToDictionary<IGrouping<string, RuntimeMetadataEntry>, string, RuntimeMetadataEntry>((IGrouping<string, RuntimeMetadataEntry> group) => group.Key, (IGrouping<string, RuntimeMetadataEntry> group) => group.First(), StringComparer.Ordinal);
			List<RuntimeMetadataEntry> source = (from @group in list.Where((RuntimeMetadataEntry entry) => entry.Category == "Firearm").GroupBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal)
				select @group.First()).ToList();
			List<RuntimeMetadataEntry> list2 = list.Where((RuntimeMetadataEntry entry) => !entry.IsModContent).ToList();
			Dictionary<string, RuntimeMetadataEntry> entriesById2 = list2.GroupBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToDictionary<IGrouping<string, RuntimeMetadataEntry>, string, RuntimeMetadataEntry>((IGrouping<string, RuntimeMetadataEntry> group) => group.Key, (IGrouping<string, RuntimeMetadataEntry> group) => group.First(), StringComparer.Ordinal);
			List<RuntimeMetadataEntry> firearms = source.Where((RuntimeMetadataEntry entry) => !entry.IsModContent).ToList();
			List<RuntimeMetadataEntry> firearms2 = source.Where((RuntimeMetadataEntry entry) => entry.IsModContent).ToList();
			List<RuntimeEnemyEntry> list3 = (from @group in sourceEnemies.Where((RuntimeEnemyEntry enemy) => enemy != null && enemy.IsSpawnable && !string.IsNullOrEmpty(enemy.EnemyNameString)).GroupBy<RuntimeEnemyEntry, string>((RuntimeEnemyEntry enemy) => enemy.EnemyNameString, StringComparer.OrdinalIgnoreCase)
				select @group.OrderByDescending((RuntimeEnemyEntry enemy) => enemy.DifficultyScore).First()).ToList();
			List<string> list4 = new List<string>();
			List<string> list5 = new List<string>();
			List<RuntimeGun> weapons = BuildWeapons(firearms, list2, entriesById2, random, list4, list5);
			List<RuntimeGun> list6 = BuildWeapons(firearms2, list, entriesById, random, list4, list5);
			RuntimeEnemy runtimeEnemy = FindRot(list3);
			List<RuntimeEnemy> list7 = BuildMixedEnemies(list3.Where((RuntimeEnemyEntry enemy) => !enemy.IsModContent));
			List<RuntimeEnemy> list8 = BuildMixedEnemies(list3);
			List<RuntimeWeaponPool> list9 = new List<RuntimeWeaponPool>();
			list9.Add(CreateScenarioPool("01_Vanilla_Rot", "Runtime 01 - Vanilla Rot", "A Rot-only random progression using active vanilla firearms.", 1, 0, weapons, runtimeEnemy));
			list9.Add(CreateScenarioPool("03_Vanilla_Mixed_Enemy", "Runtime 03 - Vanilla Mixed Enemy", "A weighted mixed-enemy progression using active vanilla firearms.", 1, 0, weapons, list7.ToArray()));
			List<RuntimeWeaponPool> list10 = list9;
			if (list6.Count > 0)
			{
				list10.Insert(1, CreateScenarioPool("02_Modded_Rot", "Runtime 02 - Modded Rot", "A Rot-only random progression using active modded firearms and compatible active feeds.", 1, 0, list6, runtimeEnemy));
				list10.Add(CreateScenarioPool("04_Modded_Mixed_Enemy", "Runtime 04 - Modded Mixed Enemy", "A weighted mixed-enemy progression using active modded firearms and active Sosigs.", 1, 0, list6, list8.ToArray()));
			}
			return new RuntimeGenerationResult
			{
				Pools = list10,
				SkippedFirearms = list4,
				FirearmsWithoutOptics = list5
			};
		}

		private static List<RuntimeGun> BuildWeapons(IEnumerable<RuntimeMetadataEntry> firearms, List<RuntimeMetadataEntry> entries, IDictionary<string, RuntimeMetadataEntry> entriesById, Random random, List<string> skipped, List<string> noOptic)
		{
			List<RuntimeGun> list = new List<RuntimeGun>();
			foreach (RuntimeMetadataEntry firearm in firearms)
			{
				List<FeedCandidate> compatibleFeeds = GetCompatibleFeeds(entries, entriesById, firearm);
				if (compatibleFeeds.Count == 0)
				{
					skipped.Add(firearm.ObjectID);
					continue;
				}
				string text = SelectOptic(entries, entriesById, firearm, random);
				if (string.IsNullOrEmpty(text))
				{
					noOptic.Add(firearm.ObjectID);
				}
				list.Add(new RuntimeGun
				{
					GunName = firearm.ObjectID,
					MagName = compatibleFeeds[0].ObjectID,
					MagNames = compatibleFeeds.Select((FeedCandidate feed) => feed.ObjectID).ToList(),
					Extra = (text ?? string.Empty),
					CategoryID = compatibleFeeds[0].CategoryID
				});
			}
			return list;
		}

		private static RuntimeEnemy FindRot(IEnumerable<RuntimeEnemyEntry> enemies)
		{
			RuntimeEnemyEntry runtimeEnemyEntry = enemies.FirstOrDefault((RuntimeEnemyEntry enemy) => string.Equals(enemy.EnemyNameString, "RW_Rot", StringComparison.OrdinalIgnoreCase));
			return new RuntimeEnemy
			{
				EnemyNameString = ((runtimeEnemyEntry == null) ? "RW_Rot" : runtimeEnemyEntry.EnemyNameString),
				Value = 1
			};
		}

		private static List<RuntimeEnemy> BuildMixedEnemies(IEnumerable<RuntimeEnemyEntry> enemies)
		{
			List<RuntimeEnemyEntry> list = enemies.OrderBy((RuntimeEnemyEntry enemy) => Math.Max(0, enemy.DifficultyScore)).ThenBy<RuntimeEnemyEntry, string>((RuntimeEnemyEntry enemy) => enemy.EnemyNameString, StringComparer.Ordinal).ToList();
			if (list.Count == 0)
			{
				return new List<RuntimeEnemy> { FindRot(list) };
			}
			List<RuntimeEnemy> list2 = new List<RuntimeEnemy>();
			foreach (RuntimeEnemyEntry item in list)
			{
				EnemySpawnWeight enemySpawnWeight = EnemyWeightPolicy.Resolve(item);
				for (int num = 0; num < enemySpawnWeight.Multiplicity; num++)
				{
					list2.Add(new RuntimeEnemy
					{
						EnemyNameString = item.EnemyNameString,
						Value = enemySpawnWeight.Value
					});
				}
			}
			return list2;
		}

		private static RuntimeWeaponPool CreateScenarioPool(string family, string name, string description, int orderType, int enemyProgressionType, IEnumerable<RuntimeGun> weapons, params RuntimeEnemy[] enemies)
		{
			return new RuntimeWeaponPool
			{
				Name = name,
				Description = description,
				OrderType = orderType,
				WeaponPoolType = "Advanced",
				EnemyProgressionType = enemyProgressionType,
				Family = family,
				EnemyType = enemies[0].EnemyNameString,
				Enemies = enemies.Select(CloneEnemy).ToList(),
				Guns = weapons.Select(CloneGun).ToList()
			};
		}

		private static RuntimeEnemy CloneEnemy(RuntimeEnemy enemy)
		{
			return new RuntimeEnemy
			{
				EnemyNameString = enemy.EnemyNameString,
				Value = enemy.Value
			};
		}

		private static RuntimeGun CloneGun(RuntimeGun gun)
		{
			return new RuntimeGun
			{
				GunName = gun.GunName,
				MagName = gun.MagName,
				MagNames = new List<string>(gun.MagNames),
				Extra = gun.Extra,
				CategoryID = gun.CategoryID
			};
		}

		private static List<FeedCandidate> GetCompatibleFeeds(List<RuntimeMetadataEntry> entries, IDictionary<string, RuntimeMetadataEntry> entriesById, RuntimeMetadataEntry firearm)
		{
			if (HasIds(firearm.CompatibleMagazines))
			{
				return GetDirectFeeds(firearm.CompatibleMagazines, entriesById, "Magazine");
			}
			if (HasIds(firearm.CompatibleClips))
			{
				return GetDirectFeeds(firearm.CompatibleClips, entriesById, "Clip");
			}
			if (HasIds(firearm.CompatibleSpeedLoaders))
			{
				return GetDirectFeeds(firearm.CompatibleSpeedLoaders, entriesById, "SpeedLoader");
			}
			if (HasIds(firearm.CompatibleSingleRounds))
			{
				return GetDirectFeeds(firearm.CompatibleSingleRounds, entriesById, "Cartridge");
			}
			if (firearm.MagazineType != 0)
			{
				return GetMatchingFeeds(entries, "Magazine", firearm.MagazineType, (RuntimeMetadataEntry entry) => entry.MagazineType, 0);
			}
			if (firearm.ClipType != 0)
			{
				List<FeedCandidate> matchingFeeds = GetMatchingFeeds(entries, "Clip", firearm.ClipType, (RuntimeMetadataEntry entry) => entry.ClipType, 1);
				if (matchingFeeds.Count > 0)
				{
					return matchingFeeds;
				}
			}
			if (firearm.RoundType != 0)
			{
				List<FeedCandidate> matchingFeeds2 = GetMatchingFeeds(entries, "SpeedLoader", firearm.RoundType, (RuntimeMetadataEntry entry) => entry.RoundType, 2);
				if (matchingFeeds2.Count <= 0)
				{
					return GetMatchingFeeds(entries, "Cartridge", firearm.RoundType, (RuntimeMetadataEntry entry) => entry.RoundType, 2);
				}
				return matchingFeeds2;
			}
			return new List<FeedCandidate>();
		}

		private static List<FeedCandidate> GetDirectFeeds(List<string> objectIds, IDictionary<string, RuntimeMetadataEntry> entriesById, string category)
		{
			List<FeedCandidate> list = new List<FeedCandidate>();
			foreach (string item in DistinctIds(objectIds))
			{
				if (entriesById.TryGetValue(item, out var value) && value.Category == category)
				{
					AddFeedCandidate(list, value);
				}
			}
			return list;
		}

		private static List<FeedCandidate> GetMatchingFeeds(IEnumerable<RuntimeMetadataEntry> entries, string category, int expectedType, Func<RuntimeMetadataEntry, int> typeSelector, int categoryId)
		{
			List<FeedCandidate> list = new List<FeedCandidate>();
			AddMatchingFeeds(list, entries, category, expectedType, typeSelector, categoryId);
			return list;
		}

		private static void AddFeedCandidate(List<FeedCandidate> candidates, RuntimeMetadataEntry feed)
		{
			int num = FeedCategoryId(feed.Category);
			if (num >= 0 && !candidates.Any((FeedCandidate candidate) => candidate.ObjectID == feed.ObjectID))
			{
				candidates.Add(new FeedCandidate(feed.ObjectID, num));
			}
		}

		private static void AddMatchingFeeds(List<FeedCandidate> candidates, IEnumerable<RuntimeMetadataEntry> entries, string category, int expectedType, Func<RuntimeMetadataEntry, int> typeSelector, int categoryId)
		{
			if (expectedType == 0)
			{
				return;
			}
			foreach (RuntimeMetadataEntry entry in entries)
			{
				if (entry.Category == category && typeSelector(entry) == expectedType)
				{
					AddFeedCandidate(candidates, entry);
				}
			}
		}

		private static string SelectOptic(List<RuntimeMetadataEntry> entries, IDictionary<string, RuntimeMetadataEntry> entriesById, RuntimeMetadataEntry firearm, Random random)
		{
			string desiredKind = DesiredOpticKind(firearm);
			List<RuntimeMetadataEntry> list = new List<RuntimeMetadataEntry>();
			foreach (string item in firearm.BespokeAttachments ?? new List<string>())
			{
				if (entriesById.TryGetValue(item, out var value) && IsCompatibleOptic(value, firearm, desiredKind))
				{
					list.Add(value);
				}
			}
			List<RuntimeMetadataEntry> list2 = PreferDedicatedCompactOptics(list, firearm);
			if (list2.Count == 0)
			{
				list2 = PreferDedicatedCompactOptics(entries.Where((RuntimeMetadataEntry attachment) => IsCompatibleOptic(attachment, firearm, desiredKind)).ToList(), firearm);
			}
			List<RuntimeMetadataEntry> list3 = (from @group in list2.GroupBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry attachment) => attachment.ObjectID, StringComparer.Ordinal)
				select @group.First()).OrderBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry attachment) => attachment.ObjectID, StringComparer.Ordinal).ToList();
			if (list3.Count == 0)
			{
				return null;
			}
			return list3[random.Next(list3.Count)].ObjectID;
		}

		private static bool IsCompatibleOptic(RuntimeMetadataEntry attachment, RuntimeMetadataEntry firearm, string desiredKind)
		{
			if (attachment.Category == "Attachment" && attachment.OpticKind == desiredKind)
			{
				return HasSharedPhysicalMount(firearm.PhysicalMountTypes, attachment.PhysicalMountTypes);
			}
			return false;
		}

		private static bool HasSharedPhysicalMount(IEnumerable<string> firearmMounts, IEnumerable<string> attachmentMounts)
		{
			if (firearmMounts == null || attachmentMounts == null)
			{
				return false;
			}
			HashSet<string> firearmMountSet = new HashSet<string>(ResolvedMountTypes(firearmMounts), StringComparer.OrdinalIgnoreCase);
			return ResolvedMountTypes(attachmentMounts).Any((string mount) => firearmMountSet.Contains(mount));
		}

		private static List<RuntimeMetadataEntry> PreferDedicatedCompactOptics(List<RuntimeMetadataEntry> candidates, RuntimeMetadataEntry firearm)
		{
			if (!IsCompact(firearm.FirearmSize))
			{
				return candidates;
			}
			List<string> dedicatedMounts = ResolvedMountTypes(firearm.PhysicalMountTypes).Where(IsDedicatedCompactOpticMount).ToList();
			if (dedicatedMounts.Count == 0)
			{
				return candidates;
			}
			return candidates.Where((RuntimeMetadataEntry attachment) => HasSharedPhysicalMount(dedicatedMounts, attachment.PhysicalMountTypes)).ToList();
		}

		private static IEnumerable<string> ResolvedMountTypes(IEnumerable<string> mounts)
		{
			return (from resolution in (mounts ?? Enumerable.Empty<string>()).Select(MountResolution.Resolve)
				where resolution.IsResolved
				select resolution.CanonicalMount).Distinct<string>(StringComparer.OrdinalIgnoreCase);
		}

		private static bool IsDedicatedCompactOpticMount(string mount)
		{
			if (!string.Equals(mount, "Picatinny", StringComparison.OrdinalIgnoreCase) && !string.Equals(mount, "Suppressor", StringComparison.OrdinalIgnoreCase) && !string.Equals(mount, "Stock", StringComparison.OrdinalIgnoreCase) && !string.Equals(mount, "M203", StringComparison.OrdinalIgnoreCase) && !string.Equals(mount, "GP25", StringComparison.OrdinalIgnoreCase) && !string.Equals(mount, "AKFore", StringComparison.OrdinalIgnoreCase) && !string.Equals(mount, "MLokRail", StringComparison.OrdinalIgnoreCase) && mount.IndexOf("Bayonet", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return mount.IndexOf("Foregrip", StringComparison.OrdinalIgnoreCase) < 0;
			}
			return false;
		}

		private static RuntimeEnemyEntry[] DefaultEnemies()
		{
			return new RuntimeEnemyEntry[1]
			{
				new RuntimeEnemyEntry
				{
					EnemyNameString = "RW_Rot",
					DisplayName = "Rot",
					IsSpawnable = true,
					DifficultyScore = 1
				}
			};
		}

		private static bool IsCompact(string firearmSize)
		{
			if (!(firearmSize == "Pocket") && !(firearmSize == "Pistol"))
			{
				return firearmSize == "Compact";
			}
			return true;
		}

		private static string DesiredOpticKind(RuntimeMetadataEntry firearm)
		{
			if (!IsCompact(firearm.FirearmSize) || !ResolvedMountTypes(firearm.PhysicalMountTypes).Any(IsDedicatedCompactOpticMount))
			{
				return "Scope";
			}
			return "Reflex";
		}

		private static List<string> DistinctIds(params List<string>[] lists)
		{
			return (from id in lists.Where((List<string> list) => list != null).SelectMany((List<string> list) => list)
				where !string.IsNullOrEmpty(id)
				select id).Distinct<string>(StringComparer.Ordinal).ToList();
		}

		private static bool HasIds(List<string> values)
		{
			return values?.Any((string value) => !string.IsNullOrEmpty(value)) ?? false;
		}

		private static int FeedCategoryId(string category)
		{
			switch (category)
			{
			case "Magazine":
				return 0;
			case "Clip":
				return 1;
			case "SpeedLoader":
			case "Cartridge":
				return 2;
			default:
				return -1;
			}
		}

		private static int RoundPowerRank(string roundPower)
		{
			return roundPower switch
			{
				"Tiny" => 1, 
				"Pistol" => 2, 
				"Shotgun" => 3, 
				"Intermediate" => 4, 
				"FullPower" => 5, 
				"AntiMaterial" => 6, 
				"Ordnance" => 7, 
				"Exotic" => 8, 
				"Fire" => 9, 
				_ => 10, 
			};
		}

		private static int SizeRank(string firearmSize)
		{
			return firearmSize switch
			{
				"Pocket" => 1, 
				"Pistol" => 2, 
				"Compact" => 3, 
				"Carbine" => 4, 
				"FullSize" => 5, 
				"Bulky" => 6, 
				"Oversize" => 7, 
				_ => 8, 
			};
		}
	}
}