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 PhysicalMountTypes { get; private set; } public RuntimeFirearmInspection(string category, int? magazineType, List 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 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 objects, string phase) { List entries = new List(); int skipped = 0; List 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(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(ref val.TagFirearmSize)/*cast due to .constrained prefix*/).ToString(), FirearmRoundPower = ((object)Unsafe.As(ref val.TagFirearmRoundPower)/*cast due to .constrained prefix*/).ToString(), FirearmMounts = ((val.TagFirearmMounts == null) ? new List() : val.TagFirearmMounts.Select((OTagFirearmMount mount) => ((object)(*(OTagFirearmMount*)(&mount))/*cast due to .constrained prefix*/).ToString()).ToList()), AttachmentMount = ((object)Unsafe.As(ref val.TagAttachmentMount)/*cast due to .constrained prefix*/).ToString(), AttachmentFeature = ((object)Unsafe.As(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 sourceEntries = entries.Where((RuntimeMetadataEntry entry) => !rules.IsBlacklisted(entry)).ToList(); int hashCode = Guid.NewGuid().GetHashCode(); List list = BuildEnemyEntries(); RuntimeGenerationResult runtimeGenerationResult = RuntimeProfileBuilder.BuildWithDiagnostics(sourceEntries, list, new Random(hashCode)); string text2 = Path.Combine(directoryName, "RuntimePools"); Directory.CreateDirectory(text2); HashSet hashSet = new HashSet(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(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 ObjectIds(IEnumerable objects) { if (objects == null) { return new List(); } return (from item in objects where (Object)(object)item != (Object)null && !string.IsNullOrEmpty(item.ItemID) select item.ItemID).Distinct().OrderBy((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(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 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 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(); } try { GameObject gameObject = ((AnvilAsset)item).GetGameObject(); if ((Object)(object)gameObject == (Object)null) { return new List(); } return GetPhysicalMountTypes(gameObject, text); } catch (Exception) { return new List(); } } private static List GetPhysicalMountTypes(GameObject prefab) { return GetPhysicalMountTypes(prefab, "FistVR.FVRFireArmAttachmentMount"); } private static List GetPhysicalMountTypes(GameObject prefab, string componentTypeName) { Type type = typeof(FVRObject).Assembly.GetType(componentTypeName, throwOnError: false); if ((object)type == null) { return new List(); } FieldInfo typeField = type.GetField("Type", BindingFlags.Instance | BindingFlags.Public); if ((object)typeField == null) { return new List(); } 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(StringComparer.Ordinal).OrderBy((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 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 list = new List(); IM instance = ManagerSingleton.Instance; if ((Object)(object)instance == (Object)null || instance.odicSosigObjsByID == null) { return list; } foreach (KeyValuePair 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 entry) => entry.EnemyNameString, StringComparer.Ordinal).ToList(); } private static int EnemyHealthScore(SosigEnemyTemplate template) { List 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()) { 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 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 list = new List(); list.AddRange(template.WeaponOptions ?? new List()); list.AddRange(template.WeaponOptions_Secondary ?? new List()); list.AddRange(template.WeaponOptions_Tertiary ?? new List()); 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(ref weapon.Category)/*cast due to .constrained prefix*/).ToString() != "Firearm") { return 1; } return ((object)Unsafe.As(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 EnemyConfigs(SosigEnemyTemplate template) { return from config in (template.ConfigTemplates ?? new List()).Concat(template.ConfigTemplates_Easy ?? new List()) 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(ref item.Category)/*cast due to .constrained prefix*/).ToString() != "Attachment") { return string.Empty; } string text = ((object)Unsafe.As(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 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 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 entries, List 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 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 values) { json.Append(",\""); AppendJsonString(json, name); json.Append("\":"); AppendJsonStringArray(json, values); } private static void AppendJsonStringArray(StringBuilder json, List values) { values = values ?? new List(); 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 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*\\[(?.*?)\\]"; Match match = Regex.Match(json, pattern, RegexOptions.Singleline); if (!match.Success) { return new string[0]; } List list = new List(); foreach (Match item in Regex.Matches(match.Groups["items"].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 CompatibleMagazines { get; set; } public List CompatibleClips { get; set; } public List CompatibleSpeedLoaders { get; set; } public List CompatibleSingleRounds { get; set; } public List BespokeAttachments { get; set; } public string FirearmSize { get; set; } public string FirearmRoundPower { get; set; } public List FirearmMounts { get; set; } public string AttachmentMount { get; set; } public string AttachmentFeature { get; set; } public string OpticKind { get; set; } public List 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 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 Enemies { get; set; } public List Guns { get; set; } } public sealed class RuntimeGenerationResult { public List Pools { get; set; } public List SkippedFirearms { get; set; } public List 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 Build(IEnumerable sourceEntries, Random random) { return BuildWithDiagnostics(sourceEntries, DefaultEnemies(), random).Pools; } public static List Build(IEnumerable sourceEntries, IEnumerable sourceEnemies, Random random) { return BuildWithDiagnostics(sourceEntries, sourceEnemies, random).Pools; } public static RuntimeGenerationResult BuildWithDiagnostics(IEnumerable sourceEntries, Random random) { return BuildWithDiagnostics(sourceEntries, DefaultEnemies(), random); } public static RuntimeGenerationResult BuildWithDiagnostics(IEnumerable sourceEntries, IEnumerable 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 list = sourceEntries.Where((RuntimeMetadataEntry entry) => entry != null && !string.IsNullOrEmpty(entry.ObjectID)).OrderBy((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToList(); Dictionary entriesById = list.GroupBy((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToDictionary, string, RuntimeMetadataEntry>((IGrouping group) => group.Key, (IGrouping group) => group.First(), StringComparer.Ordinal); List source = (from @group in list.Where((RuntimeMetadataEntry entry) => entry.Category == "Firearm").GroupBy((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal) select @group.First()).ToList(); List list2 = list.Where((RuntimeMetadataEntry entry) => !entry.IsModContent).ToList(); Dictionary entriesById2 = list2.GroupBy((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToDictionary, string, RuntimeMetadataEntry>((IGrouping group) => group.Key, (IGrouping group) => group.First(), StringComparer.Ordinal); List firearms = source.Where((RuntimeMetadataEntry entry) => !entry.IsModContent).ToList(); List firearms2 = source.Where((RuntimeMetadataEntry entry) => entry.IsModContent).ToList(); List list3 = (from @group in sourceEnemies.Where((RuntimeEnemyEntry enemy) => enemy != null && enemy.IsSpawnable && !string.IsNullOrEmpty(enemy.EnemyNameString)).GroupBy((RuntimeEnemyEntry enemy) => enemy.EnemyNameString, StringComparer.OrdinalIgnoreCase) select @group.OrderByDescending((RuntimeEnemyEntry enemy) => enemy.DifficultyScore).First()).ToList(); List list4 = new List(); List list5 = new List(); List weapons = BuildWeapons(firearms, list2, entriesById2, random, list4, list5); List list6 = BuildWeapons(firearms2, list, entriesById, random, list4, list5); RuntimeEnemy runtimeEnemy = FindRot(list3); List list7 = BuildMixedEnemies(list3.Where((RuntimeEnemyEntry enemy) => !enemy.IsModContent)); List list8 = BuildMixedEnemies(list3); List list9 = new List(); 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 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 BuildWeapons(IEnumerable firearms, List entries, IDictionary entriesById, Random random, List skipped, List noOptic) { List list = new List(); foreach (RuntimeMetadataEntry firearm in firearms) { List 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 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 BuildMixedEnemies(IEnumerable enemies) { List list = enemies.OrderBy((RuntimeEnemyEntry enemy) => Math.Max(0, enemy.DifficultyScore)).ThenBy((RuntimeEnemyEntry enemy) => enemy.EnemyNameString, StringComparer.Ordinal).ToList(); if (list.Count == 0) { return new List { FindRot(list) }; } List list2 = new List(); 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 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(gun.MagNames), Extra = gun.Extra, CategoryID = gun.CategoryID }; } private static List GetCompatibleFeeds(List entries, IDictionary 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 matchingFeeds = GetMatchingFeeds(entries, "Clip", firearm.ClipType, (RuntimeMetadataEntry entry) => entry.ClipType, 1); if (matchingFeeds.Count > 0) { return matchingFeeds; } } if (firearm.RoundType != 0) { List 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(); } private static List GetDirectFeeds(List objectIds, IDictionary entriesById, string category) { List list = new List(); foreach (string item in DistinctIds(objectIds)) { if (entriesById.TryGetValue(item, out var value) && value.Category == category) { AddFeedCandidate(list, value); } } return list; } private static List GetMatchingFeeds(IEnumerable entries, string category, int expectedType, Func typeSelector, int categoryId) { List list = new List(); AddMatchingFeeds(list, entries, category, expectedType, typeSelector, categoryId); return list; } private static void AddFeedCandidate(List 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 candidates, IEnumerable entries, string category, int expectedType, Func 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 entries, IDictionary entriesById, RuntimeMetadataEntry firearm, Random random) { string desiredKind = DesiredOpticKind(firearm); List list = new List(); foreach (string item in firearm.BespokeAttachments ?? new List()) { if (entriesById.TryGetValue(item, out var value) && IsCompatibleOptic(value, firearm, desiredKind)) { list.Add(value); } } List list2 = PreferDedicatedCompactOptics(list, firearm); if (list2.Count == 0) { list2 = PreferDedicatedCompactOptics(entries.Where((RuntimeMetadataEntry attachment) => IsCompatibleOptic(attachment, firearm, desiredKind)).ToList(), firearm); } List list3 = (from @group in list2.GroupBy((RuntimeMetadataEntry attachment) => attachment.ObjectID, StringComparer.Ordinal) select @group.First()).OrderBy((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 firearmMounts, IEnumerable attachmentMounts) { if (firearmMounts == null || attachmentMounts == null) { return false; } HashSet firearmMountSet = new HashSet(ResolvedMountTypes(firearmMounts), StringComparer.OrdinalIgnoreCase); return ResolvedMountTypes(attachmentMounts).Any((string mount) => firearmMountSet.Contains(mount)); } private static List PreferDedicatedCompactOptics(List candidates, RuntimeMetadataEntry firearm) { if (!IsCompact(firearm.FirearmSize)) { return candidates; } List 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 ResolvedMountTypes(IEnumerable mounts) { return (from resolution in (mounts ?? Enumerable.Empty()).Select(MountResolution.Resolve) where resolution.IsResolved select resolution.CanonicalMount).Distinct(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 DistinctIds(params List[] lists) { return (from id in lists.Where((List list) => list != null).SelectMany((List list) => list) where !string.IsNullOrEmpty(id) select id).Distinct(StringComparer.Ordinal).ToList(); } private static bool HasIds(List 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, }; } } }