Decompiled source of forgeweapon energylab experimental v0.0.5
plugins/ForgeEnergyLab/ForgeWeaponEnergyLabExperimental.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers.Binary; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using AIGraph; using Agents; using BepInEx; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Enemies; using FX_EffectSystem; using ForgeEnergyLab.Api; using ForgeWeaponEnergyLabExperimental; using ForgeWeaponEnergyLabExperimental.Native; using ForgeWeaponEnergyLabExperimental.Presentation; using GTFO.API; using GameData; using Gear; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using LevelGeneration; using Localization; using Microsoft.CodeAnalysis; using Player; using RootMotion.FinalIK; using SNetwork; using UnityEngine; using UnityEngine.AI; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ForgeWeaponEnergyLabExperimental")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.0.5.0")] [assembly: AssemblyInformationalVersion("0.0.5+14c29500274e00f579a60fae7f71b79615346197")] [assembly: AssemblyProduct("ForgeWeaponEnergyLabExperimental")] [assembly: AssemblyTitle("ForgeWeaponEnergyLabExperimental")] [assembly: AssemblyVersion("0.0.5.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ForgeWeaponEnergyLabExperimental { internal static class EnergyConfiguration { internal static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }; private static readonly Dictionary<string, string> Descriptions = new Dictionary<string, string> { ["magazine"] = "Whole rounds in the magazine (1..10000).", ["ammoPerRound"] = "Native reserve units per round (>0).", ["range"] = "Chain Arc first-hit distance or flame length in metres (>0); the other weapons have no range limit.", ["interval"] = "Seconds between discharges (0.01..60).", ["damage"] = "Base direct, explosion or field-pulse damage; minimum-charge damage for orbs.", ["ammoCost"] = "Whole magazine rounds per discharge; must be 1 for every weapon, at every charge power.", ["stagger"] = "Native stagger multiplier for direct/selected hits (0..100).", ["precision"] = "Native precision multiplier for direct/selected hits (0..100).", ["reloadTime"] = "Native reload duration in seconds (0.01..120).", ["equipTime"] = "Native equip transition duration in seconds (0.01..120).", ["aimTime"] = "Native aim transition duration in seconds (0.01..120).", ["targets"] = "Chain recipients including the first hit (1..4 packet budget).", ["jumpRange"] = "Maximum chain hop distance in metres (>0).", ["damageRetention"] = "Damage fraction retained after each chain hop (0..1).", ["arcLifetime"] = "Arc hit-feedback duration in seconds (>0).", ["hopDelay"] = "Visual hop delay in seconds (0..10); damage is immediate.", ["minimumCharge"] = "Minimum hold seconds to fire (>=0.1).", ["fullCharge"] = "Hold seconds for full power (>minimumCharge); holding longer keeps full power until release.", ["botChargePower"] = "Power of a native bot shot (0..1).", ["speed"] = "Projectile speed in metres/second (>0).", ["fullSpeed"] = "Full-charge projectile speed in metres/second (>0).", ["bodyRadius"] = "Projectile collision radius in metres (>0).", ["fullBodyRadius"] = "Full-charge collision radius in metres (>0).", ["travelDistance"] = "Minimum-charge flight distance in metres (>0).", ["fullTravelDistance"] = "Full-charge flight distance in metres (>0).", ["fullDamage"] = "Full-charge damage (>=damage).", ["activeProjectiles"] = "Per-definition active limit within shared slots (discs 1..3, others 1..4).", ["blastRadius"] = "Explosion radius in metres with native linear falloff (>0).", ["fullBlastRadius"] = "Full-charge explosion radius in metres (>0).", ["explosionLifetime"] = "Explosion slot/feedback duration in seconds (>0).", ["noiseRadius"] = "Native explosion noise radius in metres (0..1000).", ["explosionForce"] = "Native explosion receiver force (0..1000).", ["arcRadius"] = "Minimum-charge pulse radius in metres (>0).", ["fullArcRadius"] = "Full-charge pulse radius in metres (>0).", ["pulseInterval"] = "Seconds between damage pulses (0.01..60).", ["pulseDamageFraction"] = "Fraction of orb direct damage applied by a pulse (0..1).", ["pulseStagger"] = "Native pulse stagger multiplier (0..100).", ["pulseTargets"] = "Recipients per flight pulse (1..3 effect budget).", ["fadeLifetime"] = "Fade/slot duration after flight in seconds (>0).", ["bounces"] = "Allowed wall bounces (0..128).", ["lifetime"] = "Disc flight or gravity field duration in seconds (>0).", ["startRadius"] = "Flame cone radius at the muzzle in metres (>0).", ["radiusPerMetre"] = "Flame cone widening per metre (0..10).", ["flightLifetime"] = "Gravity flight seconds before field activation (>0).", ["gravity"] = "Downward projectile acceleration in metres/second squared (0..1000).", ["radius"] = "Gravity outer damage/pull radius in metres (>coreRadius).", ["coreRadius"] = "Full-damage inner radius in metres (>0, <radius).", ["collapseDuration"] = "Final field seconds without damage/pull (>0, <lifetime).", ["growthDuration"] = "Visible field growth duration in seconds (>0).", ["edgeDamageFraction"] = "Damage fraction at the outer field edge (0..1).", ["pullSpeed"] = "Attraction speed in metres/second (0..1000).", ["queryInterval"] = "Maximum target re-query interval in seconds (0.01..60)." }; internal const string OverridesFile = "tuning-overrides.json"; internal const string DefaultsFile = "tuning-defaults.json"; internal static JsonNode TuningJson(EnergyTuning tuning) { return JsonSerializer.SerializeToNode(tuning, tuning.GetType(), JsonOptions); } internal static WeaponDefinition[] Load(string directory, WeaponDefinition[] defaults) { Directory.CreateDirectory(directory); JsonObject jsonObject = new JsonObject(); foreach (WeaponDefinition weaponDefinition in defaults) { jsonObject.Add(weaponDefinition.Id, TuningJson(weaponDefinition.Tuning)); } File.WriteAllText(Path.Combine(directory, "tuning-defaults.json"), new JsonObject { ["_note"] = "Reference only, rewritten on every launch. Copy the values you want to change into tuning-overrides.json.", ["_descriptions"] = JsonSerializer.SerializeToNode(Descriptions, JsonOptions), ["weapons"] = jsonObject }.ToJsonString(JsonOptions), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); string path = Path.Combine(directory, "tuning-overrides.json"); if (!File.Exists(path)) { using FileStream stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(new JsonObject { ["weapons"] = new JsonObject() }.ToJsonString(JsonOptions)); } try { using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); JsonElement jsonElement = Object(Object(jsonDocument.RootElement, "$", new string[1] { "weapons" }, new string[1] { "weapons" }).GetProperty("weapons"), "$.weapons", defaults.Select((WeaponDefinition d) => d.Id).ToArray(), Array.Empty<string>()); WeaponDefinition[] array = new WeaponDefinition[defaults.Length]; for (int num = 0; num < defaults.Length; num++) { WeaponDefinition weaponDefinition2 = (array[num] = defaults[num]); if (!jsonElement.TryGetProperty(weaponDefinition2.Id, out var value)) { continue; } string text = "$.weapons." + weaponDefinition2.Id; Type type = weaponDefinition2.Tuning.GetType(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); Object(value, text, properties.Select((PropertyInfo p) => JsonOptions.PropertyNamingPolicy.ConvertName(p.Name)).ToArray(), Array.Empty<string>()); JsonObject jsonObject2 = TuningJson(weaponDefinition2.Tuning).AsObject(); PropertyInfo[] array2 = properties; foreach (PropertyInfo propertyInfo in array2) { string text2 = JsonOptions.PropertyNamingPolicy.ConvertName(propertyInfo.Name); if (!value.TryGetProperty(text2, out var value2)) { continue; } bool num2; if (value2.ValueKind == JsonValueKind.Number) { if (propertyInfo.PropertyType == typeof(int)) { num2 = !value2.TryGetInt32(out var _); goto IL_0322; } float value5; if (!(propertyInfo.PropertyType == typeof(float))) { if (value2.TryGetDouble(out var value4)) { num2 = !double.IsFinite(value4); goto IL_0322; } } else if (value2.TryGetSingle(out value5)) { num2 = !float.IsFinite(value5); goto IL_0322; } } goto IL_0324; IL_0322: if (num2) { goto IL_0324; } goto IL_035b; IL_035b: jsonObject2[text2] = JsonNode.Parse(value2.GetRawText()); continue; IL_0324: Fail(text + "." + text2, (propertyInfo.PropertyType == typeof(int)) ? "expected a 32-bit whole number" : "expected a finite number"); goto IL_035b; } EnergyTuning energyTuning = (EnergyTuning)jsonObject2.Deserialize(type, JsonOptions); ValidateTuning(weaponDefinition2.Mode, energyTuning, text); array[num] = weaponDefinition2 with { Tuning = energyTuning }; } return array; } catch (JsonException ex) { throw new InvalidDataException($"{ex.Path ?? "$"}: invalid JSON or numeric type at line {ex.LineNumber + 1}, byte {ex.BytePositionInLine + 1}", ex); } } private static JsonElement Object(JsonElement value, string path, string[] allowed, string[]? required = null) { if (value.ValueKind != JsonValueKind.Object) { Fail(path, "expected an object"); } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (JsonProperty item in value.EnumerateObject()) { if (!allowed.Contains<string>(item.Name, StringComparer.Ordinal)) { Fail(path + "." + item.Name, "unknown field"); } if (!hashSet.Add(item.Name)) { Fail(path + "." + item.Name, "duplicate field"); } } string[] array = required ?? allowed; foreach (string text in array) { if (!hashSet.Contains(text)) { Fail(path + "." + text, "missing field"); } } return value; } internal static void ValidateDefinition(WeaponDefinition definition) { if (definition == null) { throw new ArgumentNullException("definition"); } if (definition.Id == null || definition.Id.Length > 128 || !Regex.IsMatch(definition.Id, "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*){2,}$")) { Fail("id", "expected a lowercase namespaced id such as author.mod.weapon (at most 128 characters)"); } if (string.IsNullOrWhiteSpace(definition.Name) || definition.Name.Length > 128) { Fail(definition.Id + ".name", "expected a nonempty name of at most 128 characters"); } string description = definition.Description; if (description != null && (string.IsNullOrWhiteSpace(description) || description.Length > 512)) { Fail(definition.Id + ".description", "expected null or a nonempty text of at most 512 characters"); } WeaponSlot slot = definition.Slot; if ((uint)slot > 1u) { Fail(definition.Id + ".slot", "expected Main or Special"); } if (definition.Model == null || string.IsNullOrWhiteSpace(definition.Model.Key) || definition.Model.Key.Length > 128) { Fail(definition.Id + ".model.key", "expected a prefab key"); } string bundlePath = definition.Model.BundlePath; if (bundlePath != null) { if (!Path.IsPathFullyQualified(bundlePath)) { Fail(definition.Id + ".model.bundlePath", "expected an absolute bundle path"); } if (definition.Model.IconPath == null) { Fail(definition.Id + ".model.iconPath", "a provider bundle needs its own PNG icon"); } } else { EnergyGears.BuiltInModel(definition.Model.Key); } string iconPath = definition.Model.IconPath; if (iconPath != null && !Path.IsPathFullyQualified(iconPath)) { Fail(definition.Id + ".model.iconPath", "expected an absolute PNG path"); } ValidateTuning(definition.Mode, definition.Tuning, definition.Id + ".tuning"); } internal static void ValidateTuning(EnergyMode mode, EnergyTuning t, string path) { if (mode switch { EnergyMode.Beam => (t is BeamTuning) ? 1 : 0, EnergyMode.ArcChain => (t is ArcChainTuning) ? 1 : 0, EnergyMode.PlasmaBlast => (t is PlasmaBlastTuning) ? 1 : 0, EnergyMode.PlasmaArc => (t is PlasmaArcTuning) ? 1 : 0, EnergyMode.Blast => (t is BlastTuning) ? 1 : 0, EnergyMode.Disc => (t is DiscTuning) ? 1 : 0, EnergyMode.Flame => (t is FlameTuning) ? 1 : 0, EnergyMode.BlackHole => (t is BlackHoleTuning) ? 1 : 0, _ => 0, } == 0) { Fail(path, "tuning record type must match a firing behaviour"); } Check("Magazine", t.Magazine, 1.0, 10000.0); Check("AmmoCost", t.AmmoCost, 1.0, t.Magazine); if (t.AmmoCost != 1) { Fail(path + ".ammoCost", "native fire spends exactly one magazine round at every charge power"); } Check("AmmoPerRound", t.AmmoPerRound, 0.001, 1000000.0); Check("Interval", t.Interval, 0.01, 60.0); Check("Damage", t.Damage, 0.0, 1000000.0); Check("Stagger", t.Stagger, 0.0, 100.0); Check("Precision", t.Precision, 0.0, 100.0); Check("ReloadTime", t.ReloadTime, 0.01, 120.0); Check("EquipTime", t.EquipTime, 0.01, 120.0); Check("AimTime", t.AimTime, 0.01, 120.0); if (!(t is ArcChainTuning arcChainTuning)) { if (!(t is PlasmaTuning plasmaTuning)) { if (!(t is BlastTuning blastTuning)) { if (!(t is DiscTuning discTuning)) { if (!(t is FlameTuning flameTuning)) { if (t is BlackHoleTuning blackHoleTuning) { Check("Speed", blackHoleTuning.Speed, 0.01, 1000.0); Check("BodyRadius", blackHoleTuning.BodyRadius, 0.01, 100.0); Check("FlightLifetime", blackHoleTuning.FlightLifetime, 0.01, 120.0); Check("Gravity", blackHoleTuning.Gravity, 0.0, 1000.0); Check("Radius", blackHoleTuning.Radius, 0.02, 100.0); Check("CoreRadius", blackHoleTuning.CoreRadius, 0.01, (double)blackHoleTuning.Radius - 0.01); Check("Lifetime", blackHoleTuning.Lifetime, 0.02, 120.0); Check("CollapseDuration", blackHoleTuning.CollapseDuration, 0.01, (double)blackHoleTuning.Lifetime - 0.01); Check("GrowthDuration", blackHoleTuning.GrowthDuration, 0.01, 120.0); Check("PulseInterval", blackHoleTuning.PulseInterval, 0.01, 60.0); Check("EdgeDamageFraction", blackHoleTuning.EdgeDamageFraction, 0.0, 1.0); Check("PullSpeed", blackHoleTuning.PullSpeed, 0.0, 1000.0); Check("QueryInterval", blackHoleTuning.QueryInterval, 0.01, 60.0); } } else { Check("Range", flameTuning.Range, 0.01, 1000.0); Check("StartRadius", flameTuning.StartRadius, 0.01, 100.0); Check("RadiusPerMetre", flameTuning.RadiusPerMetre, 0.0, 10.0); } } else { Check("Speed", discTuning.Speed, 0.01, 1000.0); Check("BodyRadius", discTuning.BodyRadius, 0.01, 100.0); Check("Bounces", discTuning.Bounces, 0.0, 128.0); Check("Lifetime", discTuning.Lifetime, 0.01, 120.0); Check("ActiveProjectiles", discTuning.ActiveProjectiles, 1.0, 3.0); } } else { Check("BlastRadius", blastTuning.BlastRadius, 0.01, 100.0); Check("ExplosionLifetime", blastTuning.ExplosionLifetime, 0.01, 120.0); Check("ActiveProjectiles", blastTuning.ActiveProjectiles, 1.0, 4.0); Check("NoiseRadius", blastTuning.NoiseRadius, 0.0, 1000.0); Check("ExplosionForce", blastTuning.ExplosionForce, 0.0, 1000.0); } } else { Check("MinimumCharge", plasmaTuning.MinimumCharge, 0.1, 120.0); Check("FullCharge", plasmaTuning.FullCharge, plasmaTuning.MinimumCharge + 0.01, 120.0); Check("BotChargePower", plasmaTuning.BotChargePower, 0.0, 1.0); Check("Speed", plasmaTuning.Speed, 0.01, 1000.0); Check("FullSpeed", plasmaTuning.FullSpeed, 0.01, 1000.0); Check("BodyRadius", plasmaTuning.BodyRadius, 0.01, 100.0); Check("FullBodyRadius", plasmaTuning.FullBodyRadius, 0.01, 100.0); Check("TravelDistance", plasmaTuning.TravelDistance, 0.01, 1000.0); Check("FullTravelDistance", plasmaTuning.FullTravelDistance, 0.01, 1000.0); Check("FullDamage", plasmaTuning.FullDamage, plasmaTuning.Damage, 1000000.0); Check("ActiveProjectiles", plasmaTuning.ActiveProjectiles, 1.0, 4.0); if (plasmaTuning is PlasmaBlastTuning plasmaBlastTuning) { Check("BlastRadius", plasmaBlastTuning.BlastRadius, 0.01, 100.0); Check("FullBlastRadius", plasmaBlastTuning.FullBlastRadius, 0.01, 100.0); Check("ExplosionLifetime", plasmaBlastTuning.ExplosionLifetime, 0.01, 120.0); Check("NoiseRadius", plasmaBlastTuning.NoiseRadius, 0.0, 1000.0); Check("ExplosionForce", plasmaBlastTuning.ExplosionForce, 0.0, 1000.0); } if (plasmaTuning is PlasmaArcTuning plasmaArcTuning) { Check("ArcRadius", plasmaArcTuning.ArcRadius, 0.01, 100.0); Check("FullArcRadius", plasmaArcTuning.FullArcRadius, 0.01, 100.0); Check("PulseInterval", plasmaArcTuning.PulseInterval, 0.01, 60.0); Check("PulseDamageFraction", plasmaArcTuning.PulseDamageFraction, 0.0, 1.0); Check("PulseStagger", plasmaArcTuning.PulseStagger, 0.0, 100.0); Check("PulseTargets", plasmaArcTuning.PulseTargets, 1.0, 3.0); Check("FadeLifetime", plasmaArcTuning.FadeLifetime, 0.01, 120.0); } } } else { Check("Range", arcChainTuning.Range, 0.01, 1000.0); Check("Targets", arcChainTuning.Targets, 1.0, 4.0); Check("JumpRange", arcChainTuning.JumpRange, 0.01, 1000.0); Check("DamageRetention", arcChainTuning.DamageRetention, 0.0, 1.0); Check("ArcLifetime", arcChainTuning.ArcLifetime, 0.01, 120.0); Check("HopDelay", arcChainTuning.HopDelay, 0.0, 10.0); } void Check(string name, double number, double min, double max) { if (!double.IsFinite(number) || number + 1E-07 < min || number - 1E-07 > max) { Fail(path + "." + JsonOptions.PropertyNamingPolicy.ConvertName(name), FormattableString.Invariant($"expected {min}..{max}")); } } } private static void Fail(string field, string reason) { throw new InvalidDataException(field + ": " + reason); } } internal sealed class EnergyGear { internal readonly WeaponDefinition Definition; internal readonly uint GearId; internal readonly uint CategoryId; internal readonly uint ArchetypeId; internal readonly WeaponNumbers Numbers; internal readonly string ModelPath; internal EnergyMode Mode => Definition.Mode; internal string Name => Definition.Name; internal bool Main => Definition.Slot == WeaponSlot.Main; internal int Clip => Definition.Tuning.Magazine; internal float AmmoPerRound => Definition.Tuning.AmmoPerRound; internal string NativeName { get { uint gearId = GearId; return "ForgeWeaponEnergyLabExperimental_" + ((gearId >= 794201 && gearId <= 794208) ? Mode.ToString() : Definition.Id); } } internal EnergyGear(WeaponDefinition definition) { Definition = definition; ModelPath = Path.GetFullPath(definition.Model.BundlePath ?? Path.Combine(Path.GetDirectoryName(typeof(EnergyGear).Assembly.Location), "forge-energy-model-" + definition.Model.Key + ".bundle")); (uint, uint, uint) tuple = EnergyGears.Ids(definition.Id); GearId = tuple.Item1; CategoryId = tuple.Item2; ArchetypeId = tuple.Item3; Numbers = WeaponNumbers.Resolve(definition.Tuning); } } internal static class EnergyGears { private static readonly EnergyGear?[] Categories = new EnergyGear[65536]; internal static readonly Dictionary<string, string> ModelHashes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly string[] ModelKeys = new string[8] { "beam", "arc", "flame", "plasma-blast", "plasma-arc", "blast", "disc", "hole" }; private static readonly string[] BuiltInIds = new string[8] { "forge.energylab.beam", "forge.energylab.arc", "forge.energylab.flame", "forge.energylab.plasma-blast", "forge.energylab.plasma-arc", "forge.energylab.blast", "forge.energylab.disc", "forge.energylab.hole" }; internal static EnergyGear[] All { get; private set; } = Array.Empty<EnergyGear>(); internal static WeaponModel BuiltInModel(string key) { if (!Array.Exists(ModelKeys, (string model) => model == key)) { throw new ArgumentException("Unknown built-in model key: " + key, "key"); } return new WeaponModel(key); } internal static WeaponDefinition[] Defaults() { return new WeaponDefinition[8] { new WeaponDefinition(BuiltInIds[0], "Forge Beam", WeaponSlot.Main, EnergyMode.Beam, new BeamTuning(), BuiltInModel("beam")), new WeaponDefinition(BuiltInIds[1], "Forge Chain Arc", WeaponSlot.Main, EnergyMode.ArcChain, new ArcChainTuning(), BuiltInModel("arc")), new WeaponDefinition(BuiltInIds[2], "Forge Flamethrower", WeaponSlot.Main, EnergyMode.Flame, new FlameTuning(), BuiltInModel("flame")), new WeaponDefinition(BuiltInIds[3], "Forge Charge Orb", WeaponSlot.Special, EnergyMode.PlasmaBlast, new PlasmaBlastTuning(), BuiltInModel("plasma-blast")), new WeaponDefinition(BuiltInIds[4], "Forge Shock Orb", WeaponSlot.Special, EnergyMode.PlasmaArc, new PlasmaArcTuning(), BuiltInModel("plasma-arc")), new WeaponDefinition(BuiltInIds[5], "Forge Blast Gun", WeaponSlot.Special, EnergyMode.Blast, new BlastTuning(), BuiltInModel("blast")), new WeaponDefinition(BuiltInIds[6], "Forge Ricochet Disc", WeaponSlot.Special, EnergyMode.Disc, new DiscTuning(), BuiltInModel("disc")), new WeaponDefinition(BuiltInIds[7], "Forge Gravity Core", WeaponSlot.Special, EnergyMode.BlackHole, new BlackHoleTuning(), BuiltInModel("hole")) }; } internal static (uint Gear, uint Category, uint Archetype) Ids(string id) { int num = Array.IndexOf(BuiltInIds, id); if (num >= 0) { return (Gear: (uint)(794201 + num), Category: (uint)(65001 + num), Archetype: (uint)(794101 + num)); } return (Gear: 0x20000000 | (Hash(id + "/gear") & 0x1FFFFFFF), Category: 0x8000 | (Hash(id + "/category") & 0x7FFF), Archetype: 0x20000000 | (Hash(id + "/archetype") & 0x1FFFFFFF)); static uint Hash(string text) { uint num2 = 2166136261u; byte[] bytes = Encoding.UTF8.GetBytes(text); foreach (byte b in bytes) { num2 = (num2 ^ b) * 16777619; } return num2; } } internal static void Freeze() { if (All.Length != 0) { return; } EnergyGear[] array = EnergyLab.Freeze(); Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); EnergyGear[] array2 = array; foreach (EnergyGear energyGear in array2) { string iconPath = energyGear.Definition.Model.IconPath; if (iconPath != null) { FileInfo fileInfo = new FileInfo(iconPath); if (!fileInfo.Exists || fileInfo.Length < 32 || fileInfo.Length > 1048576) { throw new InvalidDataException(energyGear.Definition.Id + ".model.iconPath: missing PNG or size outside 32 B..1 MiB: " + iconPath); } } if (dictionary.ContainsKey(energyGear.ModelPath)) { continue; } FileInfo fileInfo2 = new FileInfo(energyGear.ModelPath); if (!fileInfo2.Exists || fileInfo2.Length < 1024 || fileInfo2.Length > 134217728) { throw new InvalidDataException(energyGear.Definition.Id + ".model.bundlePath: missing bundle or size outside 1 KiB..128 MiB: " + energyGear.ModelPath); } using FileStream inputStream = File.OpenRead(energyGear.ModelPath); using SHA256 sHA = SHA256.Create(); dictionary.Add(energyGear.ModelPath, Convert.ToHexString(sHA.ComputeHash(inputStream))); } array2 = array; foreach (EnergyGear energyGear2 in array2) { Categories[energyGear2.CategoryId] = energyGear2; } foreach (KeyValuePair<string, string> item in dictionary) { ModelHashes.Add(item.Key, item.Value); } All = array; } internal static EnergyGear? ForCategory(uint categoryId) { if (categoryId > 65535) { return null; } return Categories[categoryId]; } internal static EnergyMode ModeForCategory(uint categoryId) { return ForCategory(categoryId)?.Mode ?? EnergyMode.Off; } internal static bool IsSelection(uint categoryId, int mode) { if (categoryId != 0 || mode != 0) { return Match(categoryId, mode) != null; } return true; } internal static EnergyGear? Match(uint categoryId, int mode) { EnergyGear energyGear = ForCategory(categoryId); if (energyGear == null || energyGear.Mode != (EnergyMode)mode) { return null; } return energyGear; } } internal static class EnergyGearJson { internal const int SightComponent = 21; internal const uint ZoomSightPart = 25u; internal static string Build(string original, EnergyGear gear) { if (gear.CategoryId > 65535) { throw new InvalidOperationException("Gear category ID exceeds the native 16-bit packet limit."); } JsonObject jsonObject = JsonNode.Parse(original)?.AsObject() ?? throw new InvalidOperationException("Invalid native gear JSON."); jsonObject["Name"] = gear.Name; JsonObject jsonObject2 = jsonObject["Packet"]?.AsObject() ?? throw new InvalidOperationException("Native gear packet missing."); JsonObject obj = jsonObject2["Comps"]?.AsObject() ?? throw new InvalidOperationException("Native gear components missing."); bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; foreach (KeyValuePair<string, JsonNode> item in obj) { item.Deconstruct(out var _, out var value); if (value is JsonObject jsonObject3) { switch (jsonObject3["c"]?.GetValue<int>()) { case 2: jsonObject3["v"] = gear.CategoryId; flag = true; break; case 5: jsonObject3["v"] = gear.CategoryId; flag3 = true; break; case 7: jsonObject3["v"] = gear.CategoryId; flag4 = true; break; case 21: jsonObject3["v"] = 25u; flag2 = true; break; } } } if (!flag) { throw new InvalidOperationException("Native gear has no category component."); } if (!flag2) { throw new InvalidOperationException("Native gear has no sight component."); } if (!flag3 || !flag4) { throw new InvalidOperationException("Native gear has no audio or casing component."); } (jsonObject2["publicName"]?.AsObject() ?? throw new InvalidOperationException("Native gear public name missing."))["data"] = gear.Name; return jsonObject.ToJsonString(); } } internal static class SpecialWeaponMath { internal static float HoleDamageAt(float distance, in WeaponNumbers tuning) { if (!float.IsFinite(distance) || distance < 0f || distance > tuning.Radius) { return 0f; } return tuning.Pulse.Damage * (1f - (1f - tuning.EdgeDamageFraction) * Math.Clamp((distance - tuning.CoreRadius) / (tuning.Radius - tuning.CoreRadius), 0f, 1f)); } public static Vector3 Reflect(Vector3 incoming, Vector3 normal) { if (!float.IsFinite(incoming.LengthSquared()) || !float.IsFinite(normal.LengthSquared()) || incoming.LengthSquared() < 1E-06f || normal.LengthSquared() < 1E-06f) { throw new ArgumentException("Ricochet needs a travel direction and surface normal."); } return Vector3.Normalize(Vector3.Reflect(Vector3.Normalize(incoming), Vector3.Normalize(normal))); } public static bool InFlame(Vector3 start, Vector3 direction, Vector3 point, float range, in WeaponNumbers tuning) { if (!float.IsFinite(range) || range <= 0f || !float.IsFinite(direction.LengthSquared()) || direction.LengthSquared() < 1E-06f) { return false; } direction = Vector3.Normalize(direction); Vector3 vector = point - start; float num = Vector3.Dot(vector, direction); if (num < 0f || num > range) { return false; } float num2 = tuning.StartRadius + num * tuning.RadiusPerMetre; return (vector - direction * num).LengthSquared() <= num2 * num2; } public static Vector3 PullStep(Vector3 position, Vector3 center, float delta, in WeaponNumbers tuning) { Vector3 vector = center - position; float num = vector.Length(); if (!float.IsFinite(num) || !float.IsFinite(delta) || delta <= 0f || num <= tuning.CoreRadius || num > tuning.Radius) { return Vector3.Zero; } return vector / num * MathF.Min(num - tuning.CoreRadius, tuning.PullSpeed * MathF.Min(delta, 0.05f)); } } internal readonly record struct PlasmaPayload(float Speed, float BodyRadius, float TravelDistance, float Damage, float BlastRadius, float ArcRadius, float ArcDamage) { internal float Lifetime => TravelDistance / Speed; internal static PlasmaPayload FromCharge(float charge, in WeaponNumbers tuning) { if (!float.IsFinite(charge)) { throw new ArgumentOutOfRangeException("charge"); } charge = Math.Clamp(charge, 0f, 1f); float num = Lerp(tuning.Shot.Damage, tuning.FullDamage, charge); return new PlasmaPayload(Lerp(tuning.Speed, tuning.FullSpeed, charge), Lerp(tuning.BodyRadius, tuning.FullBodyRadius, charge), Lerp(tuning.TravelDistance, tuning.FullTravelDistance, charge), num, Lerp(tuning.BlastRadius, tuning.FullBlastRadius, charge), Lerp(tuning.ArcRadius, tuning.FullArcRadius, charge), num * tuning.PulseDamageFraction); static float Lerp(float low, float high, float power) { return low + (high - low) * power; } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { builder.Append("Speed = "); builder.Append(Speed.ToString()); builder.Append(", BodyRadius = "); builder.Append(BodyRadius.ToString()); builder.Append(", TravelDistance = "); builder.Append(TravelDistance.ToString()); builder.Append(", Damage = "); builder.Append(Damage.ToString()); builder.Append(", BlastRadius = "); builder.Append(BlastRadius.ToString()); builder.Append(", ArcRadius = "); builder.Append(ArcRadius.ToString()); builder.Append(", ArcDamage = "); builder.Append(ArcDamage.ToString()); return true; } } internal sealed class PlasmaContacts { public const int Capacity = 128; private readonly long[] _identities = new long[128]; private int _count; public bool Remember(long identity) { if (identity == 0L) { throw new ArgumentOutOfRangeException("identity"); } if (_identities.AsSpan(0, _count).Contains(identity)) { return false; } if (_count == 128) { throw new InvalidOperationException("Plasma contact identity budget exhausted; no truncated result used."); } _identities[_count++] = identity; return true; } public void Reset() { _count = 0; } } internal sealed class PlasmaCharge { private double? _started; public bool Charging => _started.HasValue; public bool Ready(double now, in WeaponNumbers tuning) { double? started = _started; if (started.HasValue) { double valueOrDefault = started.GetValueOrDefault(); if (double.IsFinite(now)) { return now - valueOrDefault + 1E-07 >= tuning.MinimumCharge; } } return false; } public float Power(double now, in WeaponNumbers tuning) { double? started = _started; if (started.HasValue) { double valueOrDefault = started.GetValueOrDefault(); return (float)Math.Clamp((now - valueOrDefault - tuning.MinimumCharge) / (tuning.FullCharge - tuning.MinimumCharge), 0.0, 1.0); } return 0f; } public bool Update(double now, bool held, bool pressed, bool allowed, in WeaponNumbers tuning, out float power) { power = 0f; if (!allowed || !double.IsFinite(now)) { Cancel(); return false; } if (held) { if (pressed) { double valueOrDefault = _started.GetValueOrDefault(); if (!_started.HasValue) { valueOrDefault = now; _started = valueOrDefault; } } return false; } if (!_started.HasValue) { return false; } power = Power(now, in tuning); bool result = Ready(now, in tuning); Cancel(); return result; } public void Cancel() { _started = null; } } internal readonly record struct ArcCandidate(long Identity, float DistanceSquared, bool Eligible); internal static class ArcSelection { public static int Nearest(ReadOnlySpan<ArcCandidate> candidates, ReadOnlySpan<long> visited, float range) { int num = -1; for (int i = 0; i < candidates.Length; i++) { ArcCandidate arcCandidate = candidates[i]; if (arcCandidate.Eligible && arcCandidate.Identity != 0L && !visited.Contains(arcCandidate.Identity) && float.IsFinite(arcCandidate.DistanceSquared) && !(arcCandidate.DistanceSquared < 0f) && float.IsFinite(range) && !(range < 0f) && !(arcCandidate.DistanceSquared > range * range) && (num < 0 || arcCandidate.DistanceSquared < candidates[num].DistanceSquared || (arcCandidate.DistanceSquared == candidates[num].DistanceSquared && arcCandidate.Identity < candidates[num].Identity))) { num = i; } } return num; } } internal static class EnergyProtocol { internal const string Version = "0.0.5"; internal static readonly Version Number = new Version("0.0.5"); internal const int InboxLimit = 1024; internal const float HelloInterval = 1f; internal const float HelloLifetime = 3f; internal const float MaximumMuzzleDistance = 3.5f; } internal static class EnergyLimits { internal const int ArcTargets = 4; internal const int PulseTargets = 3; internal const float Reach = 1000f; } internal readonly record struct ShotTuning(float Range, double Interval, float Damage, float Stagger, float Precision); internal readonly record struct WeaponNumbers { internal ShotTuning Shot { get; init; } internal ShotTuning Pulse { get; init; } internal int Targets { get; init; } internal float JumpRange { get; init; } internal float DamageRetention { get; init; } internal float ArcLifetime { get; init; } internal float HopDelay { get; init; } internal double MinimumCharge { get; init; } internal double FullCharge { get; init; } internal float BotChargePower { get; init; } internal float Speed { get; init; } internal float FullSpeed { get; init; } internal float BodyRadius { get; init; } internal float FullBodyRadius { get; init; } internal float TravelDistance { get; init; } internal float FullTravelDistance { get; init; } internal float FullDamage { get; init; } internal int ActiveProjectiles { get; init; } internal float BlastRadius { get; init; } internal float FullBlastRadius { get; init; } internal float ExplosionLifetime { get; init; } internal float NoiseRadius { get; init; } internal float ExplosionForce { get; init; } internal float ArcRadius { get; init; } internal float FullArcRadius { get; init; } internal float PulseDamageFraction { get; init; } internal int PulseTargets { get; init; } internal float FadeLifetime { get; init; } internal int Bounces { get; init; } internal float Lifetime { get; init; } internal float StartRadius { get; init; } internal float RadiusPerMetre { get; init; } internal float FlightLifetime { get; init; } internal float Gravity { get; init; } internal float Radius { get; init; } internal float CoreRadius { get; init; } internal float CollapseDuration { get; init; } internal float GrowthDuration { get; init; } internal float EdgeDamageFraction { get; init; } internal float PullSpeed { get; init; } internal float QueryInterval { get; init; } internal static WeaponNumbers Resolve(EnergyTuning t) { float num = ((t is ArcChainTuning arcChainTuning) ? arcChainTuning.Range : ((!(t is FlameTuning flameTuning)) ? 1000f : flameTuning.Range)); float range = num; WeaponNumbers weaponNumbers = new WeaponNumbers { Shot = new ShotTuning(range, t.Interval, t.Damage, t.Stagger, t.Precision) }; if (t is PlasmaTuning plasmaTuning) { weaponNumbers = weaponNumbers with { MinimumCharge = plasmaTuning.MinimumCharge, FullCharge = plasmaTuning.FullCharge, BotChargePower = plasmaTuning.BotChargePower, Speed = plasmaTuning.Speed, FullSpeed = plasmaTuning.FullSpeed, BodyRadius = plasmaTuning.BodyRadius, FullBodyRadius = plasmaTuning.FullBodyRadius, TravelDistance = plasmaTuning.TravelDistance, FullTravelDistance = plasmaTuning.FullTravelDistance, FullDamage = plasmaTuning.FullDamage, ActiveProjectiles = plasmaTuning.ActiveProjectiles }; } if (!(t is ArcChainTuning arcChainTuning2)) { if (!(t is PlasmaBlastTuning plasmaBlastTuning)) { if (!(t is PlasmaArcTuning plasmaArcTuning)) { if (!(t is BlastTuning blastTuning)) { if (!(t is DiscTuning discTuning)) { if (!(t is FlameTuning flameTuning2)) { if (t is BlackHoleTuning blackHoleTuning) { return weaponNumbers with { Speed = blackHoleTuning.Speed, BodyRadius = blackHoleTuning.BodyRadius, FlightLifetime = blackHoleTuning.FlightLifetime, Gravity = blackHoleTuning.Gravity, Radius = blackHoleTuning.Radius, CoreRadius = blackHoleTuning.CoreRadius, Lifetime = blackHoleTuning.Lifetime, CollapseDuration = blackHoleTuning.CollapseDuration, GrowthDuration = blackHoleTuning.GrowthDuration, Pulse = new ShotTuning(blackHoleTuning.Radius, blackHoleTuning.PulseInterval, blackHoleTuning.Damage, blackHoleTuning.Stagger, blackHoleTuning.Precision), EdgeDamageFraction = blackHoleTuning.EdgeDamageFraction, PullSpeed = blackHoleTuning.PullSpeed, QueryInterval = blackHoleTuning.QueryInterval }; } return weaponNumbers; } return weaponNumbers with { StartRadius = flameTuning2.StartRadius, RadiusPerMetre = flameTuning2.RadiusPerMetre }; } return weaponNumbers with { Speed = discTuning.Speed, BodyRadius = discTuning.BodyRadius, Bounces = discTuning.Bounces, Lifetime = discTuning.Lifetime, ActiveProjectiles = discTuning.ActiveProjectiles }; } return weaponNumbers with { BlastRadius = blastTuning.BlastRadius, FullBlastRadius = blastTuning.BlastRadius, ExplosionLifetime = blastTuning.ExplosionLifetime, ActiveProjectiles = blastTuning.ActiveProjectiles, NoiseRadius = blastTuning.NoiseRadius, ExplosionForce = blastTuning.ExplosionForce }; } return weaponNumbers with { ArcRadius = plasmaArcTuning.ArcRadius, FullArcRadius = plasmaArcTuning.FullArcRadius, Pulse = new ShotTuning(0f, plasmaArcTuning.PulseInterval, 0f, plasmaArcTuning.PulseStagger, plasmaArcTuning.Precision), PulseDamageFraction = plasmaArcTuning.PulseDamageFraction, PulseTargets = plasmaArcTuning.PulseTargets, FadeLifetime = plasmaArcTuning.FadeLifetime }; } return weaponNumbers with { BlastRadius = plasmaBlastTuning.BlastRadius, FullBlastRadius = plasmaBlastTuning.FullBlastRadius, ExplosionLifetime = plasmaBlastTuning.ExplosionLifetime, NoiseRadius = plasmaBlastTuning.NoiseRadius, ExplosionForce = plasmaBlastTuning.ExplosionForce }; } return weaponNumbers with { Targets = arcChainTuning2.Targets, JumpRange = arcChainTuning2.JumpRange, DamageRetention = arcChainTuning2.DamageRetention, ArcLifetime = arcChainTuning2.ArcLifetime, HopDelay = arcChainTuning2.HopDelay }; } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { return false; } } internal static class PcmSamples { public const int Rate = 48000; public const int MaxBytes = 1152000; public static float[] Decode(ReadOnlySpan<byte> bytes) { if (bytes.Length == 0 || (bytes.Length & 1) != 0 || bytes.Length > 1152000) { throw new ArgumentException("Invalid or oversized mono PCM resource.", "bytes"); } float[] array = new float[bytes.Length / 2]; for (int i = 0; i < array.Length; i++) { array[i] = (float)BinaryPrimitives.ReadInt16LittleEndian(bytes.Slice(i * 2, 2)) / 32768f; } return array; } } } namespace ForgeWeaponEnergyLabExperimental.Presentation { internal readonly record struct AnimationFrame(Vector3 Position, Quaternion Rotation); internal static class AnimationPose { internal static AnimationFrame Follow(in AnimationFrame restParent, in AnimationFrame currentParent, in AnimationFrame restChild) { Validate(in restParent); Validate(in currentParent); Validate(in restChild); Quaternion quaternion = Quaternion.Inverse(restParent.Rotation); Vector3 value = Vector3.Transform(restChild.Position - restParent.Position, quaternion); Quaternion rotation = Quaternion.Normalize(currentParent.Rotation * quaternion * restChild.Rotation); return new AnimationFrame(currentParent.Position + Vector3.Transform(value, currentParent.Rotation), rotation); } internal static AnimationFrame FollowHand(in AnimationFrame restGrip, in AnimationFrame currentGrip, in AnimationFrame restHand, in AnimationFrame currentHand, in AnimationFrame authoredHand) { Validate(in currentHand); AnimationFrame animationFrame = Follow(in restGrip, in currentGrip, in restHand); AnimationFrame animationFrame2 = Follow(in restGrip, in currentGrip, in authoredHand); Quaternion rotation = Quaternion.Normalize(currentHand.Rotation * Quaternion.Inverse(animationFrame.Rotation) * animationFrame2.Rotation); return new AnimationFrame(animationFrame2.Position + currentHand.Position - animationFrame.Position, rotation); } internal static AnimationFrame LimitHandReach(in AnimationFrame modelRest, in AnimationFrame donorHand, in AnimationFrame authoredHand, float holdForward) { Validate(in modelRest); Validate(in donorHand); Validate(in authoredHand); Vector3 vector = Vector3.Transform(Vector3.UnitZ, modelRest.Rotation); float num = Vector3.Dot(authoredHand.Position - donorHand.Position, vector); float num2 = num + holdForward; if (!float.IsFinite(num2)) { throw new ArgumentException("Support-hand forward reach must be finite."); } return donorHand with { Position = donorHand.Position + vector * (num - MathF.Max(0f, num2)) }; } private static void Validate(in AnimationFrame frame) { if (!PresentationContract.Finite(frame.Position) || !float.IsFinite(frame.Rotation.LengthSquared()) || MathF.Abs(frame.Rotation.LengthSquared() - 1f) > 0.001f) { throw new ArgumentException("Animation socket pose must be finite with a unit rotation."); } } } internal static class AssetRetention { internal static T Keep<T>(T asset) where T : Object { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) T val = asset; ? hideFlags = ((Object)val).hideFlags | 0x20; ((Object)val).hideFlags = (HideFlags)hideFlags; return asset; } } internal sealed class EnergyEffects : IDisposable { private const int Capacity = 4; private const float ChargeHoldGain = 0.6f; private const float BeamLoopEntry = 0.23f; private const float BeamLoopFadeIn = 0.2f; private const float BeamLoopFadeOut = 0.18f; private const int BeamLoop = 0; private const int BeamStart = 1; private const int BeamStop = 2; private const int BeamHit = 3; private const int Arc = 4; private const int ArcLoop = 5; private const int ArcHit = 6; private const int ChargeRise = 7; private const int ChargeHold = 8; private const int ChargeFull = 9; private const int PlasmaLaunch = 10; private const int ParcLaunch = 11; private const int ChargeCancel = 12; private static readonly string[] ClipNames = new string[13] { "beam-loop", "beam-start", "beam-stop", "beam-hit", "arc-fire", "arc-loop", "arc-hit", "plasma-charge-rise", "plasma-charge-loop", "plasma-charge-full", "orb-fire", "shock-fire", "plasma-charge-cancel" }; private readonly GameObject _root; private readonly Vector3[] _ends = (Vector3[])(object)new Vector3[4]; private readonly bool[] _hits = new bool[4]; private AudioSource _loop; private AudioSource _beamEvent; private AudioSource _beamStop; private AudioSource _beamHit; private AudioSource _arcEvent; private AudioSource _arcLoop; private readonly AudioSource[] _arcHits = (AudioSource[])(object)new AudioSource[4]; private readonly AudioSource[] _launchVoices = (AudioSource[])(object)new AudioSource[4]; private int _launchCursor; private AudioSource _chargeRise; private AudioSource _chargeHold; private AudioSource _chargeFull; private float _arcStarted; private float _arcLifetime; private float _arcHopDelay; private float _beamStarted; private float _beamLoopGain; private float _beamReleaseGain; private float _beamReleased = -1f; private int _segments; private bool _beaming; private bool _beamWasHit; private bool _charging; private bool _chargedFull; private float _nextBeamHit; private int _arcHitMask; private bool _disposed; private bool _arcHeld; public EnergyEffects() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown _root = new GameObject("Energy weapon effects"); Object.DontDestroyOnLoad((Object)(object)_root); try { _loop = EnergySound.Voice(_root.transform); _beamEvent = EnergySound.Voice(_root.transform); _beamStop = EnergySound.Voice(_root.transform); _beamHit = EnergySound.Voice(_root.transform, "beam-hit-voice"); _arcEvent = EnergySound.Voice(_root.transform); _arcLoop = EnergySound.Voice(_root.transform); for (int i = 0; i < _launchVoices.Length; i++) { _launchVoices[i] = EnergySound.Voice(_root.transform); } _chargeRise = EnergySound.Voice(_root.transform); _chargeHold = EnergySound.Voice(_root.transform); _chargeFull = EnergySound.Voice(_root.transform); for (int j = 0; j < 4; j++) { _arcHits[j] = EnergySound.Voice(_root.transform, "arc-hit-voice-" + j); } } catch { Dispose(); throw; } } public void Beam(Vector3 start, Vector3 end, bool hit, float now) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) _segments = 0; _root.transform.position = start; if (!_beaming) { Play(_beamEvent, 1, 0.7f); _beamStarted = now; _beaming = true; _beamReleased = -1f; if (_loop.isPlaying) { EnergySound.Stop(_loop); } } bool num = now - _beamStarted >= 0.23f; if (num && hit && (!_beamWasHit || now >= _nextBeamHit) && !_beamHit.isPlaying) { ((Component)_beamHit).transform.position = end; Play(_beamHit, 3, 0.33f); _nextBeamHit = now + 0.16f; } _beamWasHit = hit; if (num) { _beamLoopGain = (hit ? 0.63f : 0.55f) * Mathf.Clamp01((now - _beamStarted - 0.23f) / 0.2f); Hold(_loop, 0, _beamLoopGain); } } public void Charge(float power) { if (!_charging) { _charging = true; _chargedFull = false; EnergySound.Stop(_chargeFull); Play(_chargeRise, 7, 0.95f); } if (power >= 1f && !_chargedFull) { _chargedFull = true; EnergySound.Stop(_chargeRise); Play(_chargeFull, 9, 0.9f); Hold(_chargeHold, 8, 0.6f); } } public void Charge(float power, Vector3 position) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _root.transform.position = position; Charge(power); } private void Hold(AudioSource voice, int clip, float volume) { if (!EnergySound.PlayingCue(voice, ClipNames[clip])) { EnergySound.Stop(voice); } voice.pitch = 1f; if (EnergySound.PlayingCue(voice, ClipNames[clip])) { EnergySound.Gain(voice, volume); } else { EnergySound.PlayCue(voice, ClipNames[clip], volume); } } private void Play(AudioSource source, int index, float volume) { EnergySound.Stop(source); source.pitch = 1f; EnergySound.PlayCue(source, ClipNames[index], volume); } public void Discharge(bool arcOrb, Vector3 position) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) StopCharge(); _root.transform.position = position; Play(_launchVoices[_launchCursor], arcOrb ? 11 : 10, 0.8f); _launchCursor = (_launchCursor + 1) % _launchVoices.Length; } public void StopCharge() { if (_charging) { _charging = (_chargedFull = false); EnergySound.Stop(_chargeRise); EnergySound.Stop(_chargeHold); EnergySound.Stop(_chargeFull); } } public void CancelCharge() { if (_charging) { StopCharge(); Play(_chargeFull, 12, 0.45f); } } public void Chain(ReadOnlySpan<Vector3> starts, ReadOnlySpan<Vector3> ends, ReadOnlySpan<bool> hits, float now, float lifetime, float hopDelay) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (starts.Length < 1 || starts.Length > 4 || ends.Length != starts.Length || hits.Length != starts.Length) { throw new ArgumentException("Invalid chain presentation segments."); } ends.CopyTo(_ends); hits.CopyTo(_hits); _segments = starts.Length; _arcStarted = now; _arcLifetime = lifetime; _arcHopDelay = hopDelay; _arcHitMask = 0; _root.transform.position = starts[0]; Play(_arcEvent, 4, 0.82f); Tick(now); } public void ArcHold(bool held) { if (held != _arcHeld) { _arcHeld = held; if (held) { Hold(_arcLoop, 5, 0.5f); } else if (_arcLoop.isPlaying) { EnergySound.Stop(_arcLoop); } } } public void Tick(float now) { //IL_00fa: Unknown result type (might be due to invalid IL or missing references) if (_beamReleased >= 0f) { if (float.IsPositiveInfinity(_beamReleased)) { _beamReleased = now; } float num = (now - _beamReleased) / 0.18f; if (num >= 1f || !_loop.isPlaying) { EnergySound.Stop(_loop); _beamReleased = -1f; } else { EnergySound.Gain(_loop, _beamReleaseGain * (1f - num)); } } if (_segments == 0) { return; } float num2 = now - _arcStarted; if (num2 >= _arcLifetime + (float)(_segments - 1) * _arcHopDelay) { _segments = 0; return; } for (int i = 0; i < _segments; i++) { if (!(num2 < (float)i * _arcHopDelay) && _hits[i] && (_arcHitMask & (1 << i)) == 0) { _arcHitMask |= 1 << i; ((Component)_arcHits[i]).transform.position = _ends[i]; Play(_arcHits[i], 6, (i == 0) ? 0.34f : 0.22f); } } } public void Hide() { if (_segments == 0 && !_arcHeld && !_charging && !_beaming) { return; } _segments = 0; if (_arcHeld && (Object)(object)_arcLoop != (Object)null) { EnergySound.Stop(_arcLoop); } _arcHeld = false; if (_charging) { StopCharge(); } if (_beaming && !_disposed) { Play(_beamStop, 2, 0.55f); if (_loop.isPlaying) { _beamReleased = float.PositiveInfinity; _beamReleaseGain = _beamLoopGain; } } _beaming = false; _beamWasHit = false; } public void Dispose() { if (!_disposed) { _disposed = true; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } } } } internal static class EnergyIcons { private sealed class Job { internal GearIDRange Gear; internal Action<IconRenderJobResult> Callback; internal string Icon = ""; internal int Width; internal int Height; internal int Frame; } private readonly record struct Icon(Texture2D Texture, Sprite Sprite); private static readonly Dictionary<string, Icon> Icons = new Dictionary<string, Icon>(StringComparer.Ordinal); private static readonly Dictionary<uint, string> Checksums = new Dictionary<uint, string>(); private static readonly Dictionary<(string, int, int), RenderTexture> Targets = new Dictionary<(string, int, int), RenderTexture>(); private static readonly Queue<Job> Jobs = new Queue<Job>(); private static string Source(EnergyGear gear) { return gear.Definition.Model.IconPath ?? gear.Definition.Model.Key; } internal static IEnumerator Preload() { while (!EnergyGearRegistry.Ready) { yield return null; } EnergyGear[] all = EnergyGears.All; foreach (EnergyGear energyGear in all) { string text = Source(energyGear); if (Icons.ContainsKey(text)) { continue; } string iconPath = energyGear.Definition.Model.IconPath; byte[] array; if (iconPath != null) { array = File.ReadAllBytes(iconPath); } else { string text2 = "ForgeWeaponEnergyLabExperimental.Icons." + text + ".png"; using Stream stream = typeof(EnergyIcons).Assembly.GetManifestResourceStream(text2) ?? throw new InvalidDataException("Baked icon is missing: " + text2); using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } if (array.Length < 32 || array.Length > 1048576) { throw new InvalidDataException("Icon size budget exceeded: " + text); } Il2CppStructArray<byte> val = new Il2CppStructArray<byte>(array); Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, false) { name = "Energy icon " + energyGear.Definition.Id }; if (!ImageConversion.LoadImage(val2, val, true)) { throw new InvalidDataException("Cannot decode icon: " + text); } GC.KeepAlive(val); ((Texture)val2).wrapMode = (TextureWrapMode)1; ((Texture)val2).filterMode = (FilterMode)1; Icons.Add(text, new Icon(AssetRetention.Keep<Texture2D>(val2), AssetRetention.Keep<Sprite>(Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), 100f)))); yield return null; } } internal static void Register(GearIDRange gear, EnergyGear energy) { Checksums[gear.GetChecksum()] = Source(energy); } internal static bool OwnsChecksum(uint checksum) { return Checksums.ContainsKey(checksum); } internal static bool TryGetSprite(uint checksum, out Sprite sprite) { sprite = null; if (!Checksums.TryGetValue(checksum, out string value) || !Icons.TryGetValue(value, out var value2) || (Object)(object)value2.Sprite == (Object)null) { return false; } sprite = value2.Sprite; return true; } internal static bool Request(GearIDRange gear, Action<IconRenderJobResult> callback, IconRenderSettings settings) { if (gear != null) { EnergyGear energyGear = EnergyGears.ForCategory(gear.GetCompID((eGearComponent)2)); if (energyGear != null) { if ((Delegate)(object)callback == (Delegate)null) { Plugin.Error("Energy icon job has no callback: " + new ArgumentNullException("callback")); return true; } if (PresentationPreload.Failed || settings == null || settings.resX < 32 || settings.resX > 2048 || settings.resY < 32 || settings.resY > 2048 || Jobs.Count >= 64) { string message = (PresentationPreload.Failed ? "preload-failed" : ((Jobs.Count >= 64) ? "queue-full" : "invalid-size")); Plugin.Error("Energy icon request refused: " + new InvalidOperationException(message)); Complete(gear, callback, null); return true; } Jobs.Enqueue(new Job { Gear = gear, Callback = callback, Icon = Source(energyGear), Width = settings.resX, Height = settings.resY, Frame = Time.frameCount }); return true; } } return false; } internal static void Tick() { for (int i = 0; i < 4; i++) { if (Jobs.Count == 0) { break; } Job job = Jobs.Peek(); if (PresentationPreload.Failed) { Jobs.Dequeue(); Complete(job.Gear, job.Callback, null); continue; } if (job.Frame >= Time.frameCount || (!PresentationPreload.Ready && !Icons.ContainsKey(job.Icon))) { break; } Jobs.Dequeue(); RenderTexture target = null; try { if (!Icons.TryGetValue(job.Icon, out var value)) { throw new InvalidOperationException("Energy icon is unavailable: " + job.Icon); } target = Target(job.Icon, value.Texture, job.Width, job.Height); } catch (Exception ex) { Plugin.Error("Energy icon rendering failed: " + ex); } Complete(job.Gear, job.Callback, target); } } private static void Complete(GearIDRange gear, Action<IconRenderJobResult> callback, RenderTexture? target) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown try { callback.Invoke(new IconRenderJobResult { gearID = gear, icon = target }); } catch (Exception ex) { Plugin.Error("Energy icon callback failed: " + ex); } } private static RenderTexture Target(string icon, Texture2D texture, int width, int height) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) (string, int, int) key = (icon, width, height); if (Targets.TryGetValue(key, out RenderTexture value) && (Object)(object)value != (Object)null && value.IsCreated()) { return value; } if (Targets.Count >= 64 && !Targets.ContainsKey(key)) { throw new InvalidOperationException("Energy preview target budget exceeded."); } RenderTexture val = (((Object)(object)value != (Object)null) ? value : AssetRetention.Keep<RenderTexture>(new RenderTexture(width, height, 0, (RenderTextureFormat)0) { name = "Energy preview " + ((Object)texture).name })); bool flag = (Object)(object)value == (Object)null; RenderTexture active = RenderTexture.active; try { try { if (!val.Create()) { throw new InvalidOperationException("Cannot create energy preview target."); } float num = (float)((Texture)texture).width / (float)((Texture)texture).height; float num2 = (float)width / (float)height; Vector2 val2 = ((num2 > num) ? new Vector2(1f, num / num2) : new Vector2(num2 / num, 1f)); Graphics.Blit((Texture)(object)texture, val, val2, (Vector2.one - val2) * 0.5f); Targets[key] = val; return val; } finally { RenderTexture.active = active; } } catch { try { val.Release(); } catch (Exception ex) { Plugin.Error("Energy preview target release failed: " + ex); } if (flag) { try { Object.Destroy((Object)(object)val); } catch (Exception ex2) { Plugin.Error("Energy preview target destruction failed: " + ex2); } } throw; } } } internal readonly struct LightSpec { internal readonly Color Color; internal readonly float Range; internal readonly float Intensity; internal readonly float Life; internal readonly float Flicker; internal LightSpec(Color color, float range, float intensity, float life = 0f, float flicker = 0.1f) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Color = color; Range = range; Intensity = intensity; Life = life; Flicker = flicker; } } internal static class EnergyLights { internal const int Capacity = 16; private const float HoldFadeIn = 0.04f; private const float HoldGrace = 0.06f; private const float HoldFadeOut = 0.12f; private static readonly FX_PointLight?[] Lights = (FX_PointLight?[])(object)new FX_PointLight[16]; private static readonly Vector3[] Positions = (Vector3[])(object)new Vector3[16]; private static readonly float[] Born = new float[16]; private static readonly float[] Seen = new float[16]; private static readonly float[] Life = new float[16]; private static readonly float[] Peak = new float[16]; private static readonly float[] Strength = new float[16]; private static readonly float[] Flicker = new float[16]; private static readonly bool[] Held = new bool[16]; private static readonly int[] Generation = new int[16]; internal static void Flash(Vector3 position, in LightSpec spec, float now, float strength = 1f, float delay = 0f) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) int num = Allocate(in spec, now); if (num >= 0) { Held[num] = false; Positions[num] = position; Born[num] = now + delay; Life[num] = Mathf.Max(0.02f, spec.Life); Strength[num] = strength; } } internal static void Hold(ref int handle, Vector3 position, in LightSpec spec, float now, float strength = 1f) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) int num = Resolve(handle); if (num < 0 || !Held[num]) { num = Allocate(in spec, now); if (num < 0) { handle = 0; return; } Held[num] = true; Born[num] = now; handle = ((Generation[num] & 0x7FFFFF) << 8) | (num + 1); } Positions[num] = position; Seen[num] = now; Strength[num] = strength; } internal static void Release(ref int handle) { int num = Resolve(handle); if (num >= 0 && Held[num]) { Seen[num] = float.MinValue; } handle = 0; } internal static void Tick(float now) { //IL_0146: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 16; i++) { FX_PointLight val = Lights[i]; if (val == null) { continue; } if ((Object)(object)val == (Object)null || !val.m_isInUse) { Lights[i] = null; Generation[i]++; continue; } float num2; if (Held[i]) { float num = now - Seen[i]; num2 = Mathf.Clamp01((now - Born[i]) / 0.04f) * ((num <= 0.06f) ? 1f : (1f - Mathf.Clamp01((num - 0.06f) / 0.12f))); if (num > 0.17999999f) { Free(i); continue; } } else { float num3 = now - Born[i]; if (num3 >= Life[i]) { Free(i); continue; } float num4 = ((num3 < 0f) ? 0f : (1f - num3 / Life[i])); num2 = ((num3 < 0f) ? 0f : (num4 * num4)); } float num5 = 1f - Flicker[i] * Mathf.PerlinNoise(now * 14f, (float)i * 3.7f); val.m_intensity = Peak[i] * Strength[i] * num2 * num5; val.m_position = Positions[i]; ((CL_Light)val).UpdateData(); ((CL_Light)val).UpdateTransform(); } } internal static void Clear() { for (int i = 0; i < 16; i++) { if (Lights[i] != null) { Free(i); } } } private static int Resolve(int handle) { if (handle == 0) { return -1; } int num = (handle & 0xFF) - 1; if (num < 0 || num >= 16 || Lights[num] == null || (Generation[num] & 0x7FFFFF) != handle >> 8) { return -1; } return num; } private static int Allocate(in LightSpec spec, float now) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) int num = -1; for (int i = 0; i < 16; i++) { if (Lights[i] == null) { num = i; break; } } if (num < 0) { float num2 = float.MaxValue; for (int j = 0; j < 16; j++) { if (!Held[j]) { float num3 = Born[j] + Life[j] - now; if (num3 < num2) { num2 = num3; num = j; } } } if (num < 0) { return -1; } } FX_PointLight val = Lights[num]; if (val == null || (Object)(object)val == (Object)null || !val.m_isInUse) { Lights[num] = null; FX_PointLight val2 = default(FX_PointLight); if (!FX_Manager.TryAllocateFXLight(ref val2, false) || (Object)(object)val2 == (Object)null) { return -1; } val = (Lights[num] = val2); } Generation[num]++; val.SetColor(spec.Color); val.SetRange(spec.Range); val.m_intensity = 0f; Peak[num] = spec.Intensity; Flicker[num] = spec.Flicker; return num; } private static void Free(int slot) { FX_PointLight val = Lights[slot]; Lights[slot] = null; Generation[slot]++; if (val != null && !((Object)(object)val == (Object)null) && val.m_isInUse) { val.m_intensity = 0f; ((CL_Light)val).UpdateData(); FX_Manager.DeallocateFXLight(val); } } } internal static class EnergySound { internal readonly struct Cue { internal readonly string Name; internal readonly bool Loop; internal readonly bool Spatial; internal readonly float Near; internal readonly float Far; internal Cue(string name, bool loop, bool spatial, float near, float far) { Name = name; Loop = loop; Spatial = spatial; Near = near; Far = far; } } private const float Mix = 2f; private const float PanWidth = 0.75f; private const int VoiceCapacity = 96; internal static readonly Cue[] Cues = new Cue[38] { Clip("beam-start", 2f, 30f), Loop("beam-loop", 2f, 30f), Clip("beam-stop", 2f, 30f), Clip("beam-hit", 1.5f, 25f), Clip("arc-fire", 1f, 24f), Loop("arc-loop", 2f, 24f), Clip("arc-hit", 1f, 24f), Clip("plasma-charge-rise", 2f, 24f), Loop("plasma-charge-loop", 2f, 24f), Clip("plasma-charge-full", 2f, 24f), Clip("plasma-charge-cancel", 2f, 24f), Clip("orb-fire", 2f, 30f), Loop("orb-fly-loop", 2f, 30f), Loop("shock-fly-loop", 2f, 30f), Clip("orb-explode", 1.5f, 36f), Clip("shock-fire", 2f, 30f), Clip("shock-fade", 1.5f, 30f), Clip("shock-hit", 1f, 22f), Clip("shock-pulse", 1f, 24f), Clip("blast-fire", 2f, 30f), Clip("blast-cycle", 2f, 30f), Clip("blast-explode", 6f, 48f), Clip("disc-fire", 2f, 30f), Loop("disc-fly-loop", 4f, 36f), Clip("disc-bounce", 4f, 36f), Clip("disc-drop", 3f, 30f), Clip("flame-start", 2f, 24f), Loop("flame-loop", 2f, 24f), Clip("flame-stop", 2f, 24f), Clip("flame-hit", 3f, 18f), Clip("flame-crackle", 3f, 18f), Clip("gravity-fire", 2f, 30f), Loop("gravity-fly-loop", 3f, 30f), Clip("gravity-open", 5f, 40f), Clip("gravity-close", 5f, 40f), Loop("gravity-field-loop", 5f, 40f), Clip("energy-dryfire"), Clip("mech-dryfire") }; private static readonly string[] SelectedFiles = ReadSelectedFiles(); private static readonly Dictionary<string, AudioClip> Loaded = new Dictionary<string, AudioClip>(StringComparer.Ordinal); private static int _volumeFrame = -1; private static float _volume; private static readonly AudioSource?[] Voices = (AudioSource?[])(object)new AudioSource[96]; private static readonly int[] VoiceCues = new int[96]; private static readonly float[] VoiceGains = new float[96]; private static int _voiceCount; private static bool _hasEar; private static Vector3 _ear; private static Vector3 _earRight; internal static float MasterVolume { get { int frameCount = Time.frameCount; if (frameCount == _volumeFrame) { return _volume; } _volumeFrame = frameCount; _volume = 2f * GameVolume(); return _volume; } } private static Cue Clip(string name) { return new Cue(name, loop: false, spatial: false, 0f, 0f); } private static Cue Clip(string name, float near, float far) { return new Cue(name, loop: false, spatial: true, near, far); } private static Cue Loop(string name, float near, float far) { return new Cue(name, loop: true, spatial: true, near, far); } private static float GameVolume() { try { CellSettingsData settingsData = CellSettingsManager.SettingsData; CellSettings_Audio val = ((settingsData != null) ? settingsData.Audio : null); if (((val != null) ? val.MasterVolume : null) == null || val.SFXVolume == null) { return Unreadable(new InvalidOperationException("Audio settings are not loaded.")); } CS_Value<bool> muteInBackground = val.MuteInBackground; if (muteInBackground != null && muteInBackground.Value && !Application.isFocused) { return 0f; } return Clamp01(val.MasterVolume.Value) * Clamp01(val.SFXVolume.Value); } catch (Exception error) { return Unreadable(error); } } private static float Unreadable(Exception error) { Plugin.Error("Game volume unreadable; energy sounds muted: " + error); return 0f; } private static float Clamp01(float value) { if (!float.IsNaN(value) && !(value < 0f)) { if (!(value > 1f)) { return value; } return 1f; } return 0f; } internal static int IndexOf(string cue) { for (int i = 0; i < Cues.Length; i++) { if (Cues[i].Name == cue) { return i; } } throw new ArgumentException("Unknown energy sound cue: " + cue, "cue"); } internal static AudioSource Voice(Transform parent, string? name = null) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (name != null) { Transform transform = new GameObject(name).transform; transform.SetParent(parent, false); parent = transform; } AudioSource obj = ((Component)parent).gameObject.AddComponent<AudioSource>(); obj.playOnAwake = false; return obj; } internal static void PlaySpatial(AudioSource[] voices, ref int cursor, string cue, Vector3 position, float gain) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) AudioSource val = null; for (int i = 0; i < voices.Length; i++) { int num = (cursor + i) % voices.Length; AudioSource val2 = voices[num]; if (!((Object)(object)val2 == (Object)null) && !val2.isPlaying) { val = val2; cursor = (num + 1) % voices.Length; break; } } if (val == null) { val = voices[cursor++ % voices.Length]; } if (!((Object)(object)val == (Object)null)) { ((Component)val).transform.position = position; Stop(val); PlayCue(val, cue, gain); } } internal static void PlayCue(AudioSource source, string cue, float gain) { int num = IndexOf(cue); Cue cue2 = Cues[num]; int num2 = Slot(source); if (num2 < 0) { Stop(source); return; } VoiceCues[num2] = num; VoiceGains[num2] = gain; source.loop = cue2.Loop; source.dopplerLevel = 0f; source.spatialBlend = 0f; AudioClip val = LoadSelected(num); if ((Object)(object)source.clip != (Object)(object)val) { source.clip = val; } Apply(num2); source.Play(); } internal static bool PlayingCue(AudioSource source, string cue) { if (!source.isPlaying) { return false; } int num = IndexOf(cue); for (int i = 0; i < _voiceCount; i++) { if (Voices[i] == source) { return VoiceCues[i] == num; } } return false; } internal static void Gain(AudioSource source, float gain) { int num = Find(source); if (num >= 0 && source.isPlaying) { VoiceGains[num] = gain; Apply(num); } } internal static void Listen(Transform? ear) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) _hasEar = ear != null && (Object)(object)ear != (Object)null; if (_hasEar) { _ear = ear.position; _earRight = ear.right; } int num = 0; for (int i = 0; i < _voiceCount; i++) { AudioSource val = Voices[i]; if (val != null && !((Object)(object)val == (Object)null) && val.isPlaying) { Voices[num] = val; VoiceCues[num] = VoiceCues[i]; VoiceGains[num] = VoiceGains[i]; Apply(num); num++; } } for (int j = num; j < _voiceCount; j++) { Voices[j] = null; } _voiceCount = num; } private static int Slot(AudioSource source) { int num = Find(source); if (num >= 0) { return num; } for (int num2 = _voiceCount - 1; num2 >= 0; num2--) { if ((Object)(object)Voices[num2] == (Object)null || !Voices[num2].isPlaying) { Release(num2); } } if (_voiceCount == 96) { return -1; } Voices[_voiceCount] = source; return _voiceCount++; } private static int Find(AudioSource source) { for (int i = 0; i < _voiceCount; i++) { if (Voices[i] == source) { return i; } } return -1; } private static void Release(int slot) { int num = --_voiceCount; Voices[slot] = Voices[num]; VoiceCues[slot] = VoiceCues[num]; VoiceGains[slot] = VoiceGains[num]; Voices[num] = null; } internal static void Stop(AudioSource source) { if ((Object)(object)source != (Object)null) { source.Stop(); } int num = Find(source); if (num >= 0) { Release(num); } } private static void Apply(int slot) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) AudioSource val = Voices[slot]; Cue entry = Cues[VoiceCues[slot]]; float num = VoiceGains[slot] * MasterVolume; if (!entry.Spatial) { val.volume = Math.Min(1f, num); val.panStereo = 0f; } else { Place(in entry, ((Component)val).transform.position, out var heard, out var pan); val.volume = Math.Min(1f, num * heard); val.panStereo = pan; } } internal static void Place(in Cue entry, Vector3 position, out float heard, out float pan) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) heard = 0f; pan = 0f; if (_hasEar) { Vector3 val = position - _ear; float magnitude = ((Vector3)(ref val)).magnitude; heard = ((magnitude <= entry.Near) ? 1f : ((magnitude >= entry.Far) ? 0f : (1f - (magnitude - entry.Near) / (entry.Far - entry.Near)))); if (!(magnitude < 0.01f)) { float value = (val.x * _earRight.x + val.y * _earRight.y + val.z * _earRight.z) / magnitude; pan = Math.Clamp(value, -1f, 1f) * 0.75f * Math.Min(1f, magnitude / entry.Near); } } } internal static AudioClip Load(string name) { return LoadSelected(IndexOf(name)); } private static string[] ReadSelectedFiles() { using Stream stream = typeof(EnergySound).Assembly.GetManifestResourceStream("ForgeWeaponEnergyLabExperimental.Audio.variants.tsv") ?? throw new InvalidOperationException("Generated audio variant map is missing from this build."); using StreamReader streamReader = new StreamReader(stream); string[] array = new string[Cues.Length]; string text; while ((text = streamReader.ReadLine()) != null) { string[] array2 = text.Split('\t'); if (array2.Length != 2 || !array2[1].EndsWith(".bin", StringComparison.Ordinal)) { throw new InvalidDataException("Malformed generated audio variant map."); } int num = IndexOf(array2[0]); if (array[num] != null) { throw new InvalidDataException("Duplicate recording for cue " + array2[0]); } array[num] = array2[1]; } for (int i = 0; i < array.Length; i++) { if (array[i] == null) { throw new InvalidDataException("Missing recording for cue " + Cues[i].Name); } } return array; } private static AudioClip LoadSelected(int index) { string text = SelectedFiles[index]; if (Loaded.TryGetValue(text, out AudioClip value) && (Object)(object)value != (Object)null) { return value; } if (PresentationPreload.Ready) { throw new InvalidOperationException("Energy sound was not prepared: " + text); } using Stream stream = typeof(EnergySound).Assembly.GetManifestResourceStream("ForgeWeaponEnergyLabExperimental.Audio.Variants." + text + ".gz") ?? throw new InvalidOperationException("Missing selected energy sound: " + text); if (stream.Length > 1152000) { throw new InvalidDataException("Energy sound exceeds the resource budget."); } using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true)) { byte[] array = new byte[8192]; int num; while ((num = gZipStream.Read(array, 0, array.Length)) > 0) { if (memoryStream.Length + num > 1152000) { throw new InvalidDataException("Decoded energy sound exceeds the resource budget."); } memoryStream.Write(array, 0, num); } } float[] array2 = PcmSamples.Decode(memoryStream.GetBuffer().AsSpan(0, checked((int)memoryStream.Length))); AudioClip val = AssetRetention.Keep<AudioClip>(AudioClip.Create("energy-" + Cues[index].Name + "-0", array2.Length, 1, 48000, false)); try { if (val.channels != 1 || val.samples != array2.Length || val.frequency != 48000) { throw new InvalidOperationException("Unity created an unexpected audio buffer for " + text); } Il2CppStructArray<float> val2 = new Il2CppStructArray<float>(array2); try { if (!AudioClip.SetData(val, val2, array2.Length, 0)) { throw new InvalidOperationException("Unity rejected the energy audio buffer: " + text); } int num2 = Math.Min(32, array2.Length); int num3 = (array2.Length - num2) / 2; Il2CppStructArray<float> val3 = new Il2CppStructArray<float>((long)num2); if (!AudioClip.GetData(val, val3, num2, num3)) { throw new InvalidOperationException("Unity could not read back the energy audio buffer: " + text); } for (int i = 0; i < num2; i++) { if (!float.IsFinite(((Il2CppArrayBase<float>)(object)val3)[i]) || MathF.Abs(((Il2CppArrayBase<float>)(object)val3)[i] - array2[num3 + i]) > 0.0001f) { throw new InvalidOperationException("Unity energy audio readback differs: " + text); } } GC.KeepAlive(val3); } finally { GC.KeepAlive(val2); } Loaded[text] = val; return val; } catch { Object.Destroy((Object)(object)val); throw; } } } internal sealed class EnergyVisuals : IDisposable { private const string BeamMember = "forge_energy_beam"; private const string ArcMember = "forge_lightning_arc"; private const string IgniteState = "ignite"; private static readonly LightSpec BeamMuzzleLight = new LightSpec(new Color(0.4f, 0.7f, 1f), 3f, 1.5f, 0f, 0.2f); private static readonly LightSpec BeamImpactLight = new LightSpec(new Color(0.45f, 0.75f, 1f), 4.5f, 2.5f, 0f, 0.3f); private static readonly LightSpec ArcLight = new LightSpec(new Color(0.5f, 0.75f, 1f), 5f, 3f, 0.2f, 0.4f); private static readonly LightSpec BlastMuzzleLight = new LightSpec(new Color(1f, 0.6f, 0.25f), 4f, 3f, 0.12f); private const int Capacity = 4; private readonly VfxPool _pool = new VfxPool("Energy weapon visuals"); private readonly Vector3[] _starts = (Vector3[])(object)new Vector3[4]; private readonly Vector3[] _ends = (Vector3[])(object)new Vector3[4]; private readonly bool[] _hits = new bool[4]; private VfxInstance? _beam; private int _beamMuzzleLight; private int _beamImpactLight; private float _arcStarted; private float _arcHopDelay; private int _segments; private int _started; private bool _disposed; private bool _prepared; public EnergyVisuals() { VfxLibrary.Prefab("forge_energy_beam"); VfxLibrary.Prefab("forge_lightning_arc"); } internal IEnumerator Prepare() { if (_prepared) { yield break; } foreach (object item in _pool.Prepare("forge_energy_beam", 2)) { yield return item; } foreach (object item2 in _pool.Prepare("forge_lightning_arc", 8)) { yield return item2; } _prepared = true; } public void Beam(Vector3 start, Vector3 end, bool hit, float now) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (!_disposed) { _segments = 0; bool num = _beam == null; if (_beam == null) { _beam = _pool.Take("forge_energy_beam").Hold(); } _beam.Stretch(start, end); _beam.Emit("impact", hit); _beam.Keep(); if (num) { _beam.PlayState("ignite"); } EnergyLights.Hold(ref _beamMuzzleLight, start, in BeamMuzzleLight, now); if (hit) { EnergyLights.Hold(ref _beamImpactLight, end, in BeamImpactLight, now); } else { EnergyLights.Release(ref _beamImpactLight); } } } public void Chain(ReadOnlySpan<Vector3> starts, ReadOnlySpan<Vector3> ends, ReadOnlySpan<bool> hits, float now, float hopDelay) { if (starts.Length < 1 || starts.Length > 4 || ends.Length != starts.Length || hits.Length != starts.Length) { throw new ArgumentException("Invalid chain presentation segments."); } if (!_disposed) { Hide(); starts.CopyTo(_starts); ends.CopyTo(_ends); hits.CopyTo(_hits); _segments = starts.Length; _started = 0; _arcStarted = now; _arcHopDelay = hopDelay; Tick(now); } } public void Blast(Vector3 start, float now) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!_disposed) { Hide(); EnergyLights.Flash(start, in BlastMuzzleLight, now); } } public void Tick(float now) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (_disposed) { return; } while (_started < _segments && now - _arcStarted >= (float)_started * _arcHopDelay) { VfxInstance vfxInstance = _pool.Take("forge_lightning_arc"); vfxInstance.Stretch(_starts[_started], _ends[_started]); vfxInstance.Emit("end", _hits[_started]); vfxInstance.Start(); if (_hits[_started]) { EnergyLights.Flash(_ends[_started], in ArcLight, now); } _started++; } } public void Hide() { if (_segments != 0 || _beam != null || _beamMuzzleLight != 0 || _beamImpactLight != 0) { _segments = (_started = 0); _beam?.Release(); _beam = null; EnergyLights.Release(ref _beamMuzzleLight); EnergyLights.Release(ref _beamImpactLight); } } public void Dispose() { if (!_disposed) { _disposed = true; _pool.Dispose(); } } } internal static class ModelSockets { internal static Transform Require(Transform model, string name) { return Find(model, name) ?? throw new InvalidDataException("Energy model socket missing: " + name); } internal static Transform? Find(Transform model, string name) { if ((Object)(object)model == (Object)null) { throw new ArgumentNullException("model"); } if (string.IsNullOrEmpty(name)) { throw new ArgumentException("Socket name is required.", "name"); } Transform val = null; for (int i = 0; i < model.childCount; i++) { Transform child = model.GetChild(i); if (!(((Object)child).name != name) && !((Object)(object)((Component)child).GetComponent<MeshFilter>() != (Object)null) && !((Object)(object)((Component)child).GetComponent<Renderer>() != (Object)null)) { if ((Object)(object)val != (Object)null) { throw new InvalidDataException("Energy model socket is ambiguous: " + name); } val = child; } } return val; } } internal sealed class PlasmaEffects : IDisposable { private const string OrbMember = "forge_plasma_orb"; private const string DischargeMember = "forge_plasma_discharge"; private const string ExplosionMember = "forge_plasma_explosion"; private const string ChargingState = "charging"; private const string ChargedState = "charged"; private const string LaunchState = "launch"; private const string StormState = "storm"; private const string FizzleState = "fizzle"; private const float ExplosionStandOff = 0.3f; private static readonly LightSpec OrbLight = new LightSpec(new Color(0.25f, 0.55f, 1f), 4f, 1.4f, 0f, 0.25f); private static readonly LightSpec ElectricOrbLight = new LightSpec(new Color(0.5f, 0.35f, 1f), 4f, 1.4f, 0f, 0.45f); private static readonly LightSpec ChargedLight = new LightSpec(new Color(0.3f, 0.6f, 1f), 3f, 2.5f, 0.2f); private static readonly LightSpec LaunchLight = new LightSpec(new Color(0.3f, 0.6f, 1f), 5f, 3f, 0.15f); private static readonly LightSpec DischargeLight = new LightSpec(new Color(0.55f, 0.4f, 1f), 3f, 2f, 0.12f, 0.5f); private static readonly LightSpec ExplosionLight = new LightSpec(new Color(0.35f, 0.6f, 1f), 7f, 3f, 0.55f, 0.2f); private const float OrbSize = 0.3f; private const float ExplosionSize = 3f; private static readonly Color ElectricTint = new Color(1f, 0.7f, 1f); private readonly VfxPool _pool = new VfxPool("Plasma ionization effects"); private readonly VfxInstance?[] _orbs; private readonly bool[] _exploded; private readonly Vector3[] _positions; private readonly bool[] _electric; private readonly bool[] _flying; private readonly int[] _lights; private readonly GameObject _voices; private readonly AudioSource[] _flightVoices; private readonly AudioSource[] _impactVoices = (AudioSource[])(object)new AudioSource[4]; private readonly AudioSource[] _arcVoices = (AudioSource[])(object)new AudioSource[6]; private int _impactCursor; private int _arcCursor; private bool _disposed; private bool _prepared; private bool Ready => !_disposed; public PlasmaEffects(int capacity) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown _orbs = new VfxInstance[capacity]; _exploded = new bool[capacity]; _positions = (Vector3[])(object)new Vector3[capacity]; _electric = new bool[capacity]; _flying = new bool[capacity]; _lights = new int[capacity]; _flightVoices = (AudioSource[])(object)new AudioSource[capacity]; _voices = new GameObject("Plasma ionization voices"); Object.DontDestroyOnLoad((Object)(object)_voices); try { VfxLibrary.Prefab("forge_plasma_orb"); VfxLibrary.Prefab("forge_plasma_discharge"); VfxLibrary.Prefab("forge_plasma_explosion"); for (int i = 0; i < _flightVoices.Length; i++) { _flightVoices[i] = EnergySound.Voice(_voices.transform, "plasma-flight-voice-" + i); } for (int j = 0; j < _impactVoices.Length; j++) { _impactVoices[j] = EnergySound.Voice(_voices.transform, "plasma-impact-voice-" + j); } for (int k = 0; k < _arcVoices.Length; k++) { _arcVoices[k] = EnergySound.Voice(_voices.transform, "plasma-arc-voice-" + k); } } catch { Dispose(); throw; } } private static bool Alive(Object? item) { if (item != null) { return item != (Object)null; } return false; } internal IEnumerator Prepare() { if (_prepared) { yield break; } foreach (object item in _pool.Prepare("forge_plasma_orb", _orbs.Length * 2)) { yield return item; } foreach (object item2 in _pool.Prepare("forge_plasma_orb", _orbs.Length * 2, ElectricTint)) { yield return item2; } foreach (object item3 in _pool.Prepare("forge_plasma_discharge", _orbs.Length * 3 * 2)) { yield return item3; } foreach (object item4 in _pool.Prepare("forge_plasma_explosion", _orbs.Length * 2)) { yield return item4; } _prepared = true; } public void ChargingOrb(int index, Vector3 position, float radius, float now, bool electric) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) Orb(index, position, radius * 0.55f, now, electric, flying: false); } public void Orb(int index, Vector3 position, float radius, float now, bool electric, bool flying) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!Ready) { return; } Draw(index, position, radius, electric, flying, now); if (!flying) { return; } AudioSource val = _flightVoices[index]; if (Alive((Object?)(object)val)) { ((Component)val).transform.position = position; if (!val.isPlaying) { val.pitch = 1f; EnergySound.PlayCue(val, electric ? "shock-fly-loop" : "orb-fly-loop", 0.85f); } } } public void Fade(int index, Vector3 position) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (Ready) { StopFlight(index); _orbs[index]?.PlayState("fizzle"); EnergySound.PlaySpatial(_arcVoices, ref _arcCursor, "shock-fade", position, 0.5f); } } private void StopFlight(int index) { AudioSource val = _flightVoices[index]; if (Alive((Object?)(object)val) && val.isPlaying) { EnergySound.Stop(val); } } public void Contact(Vector3 position) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (Ready) { EnergySound.PlaySpatial(_arcVoices, ref _arcCursor, "shock-hit", position, 0.22f); } } public void Dissipate(int index, Vector3 position, float radius, float progress, float now) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (Ready) { Draw(index, position, radius * (1f - Mathf.Clamp01(progress) * 0.9f), electric: true, flying: true, now); } } public void Explosion(int index, Vector3 position, float blastRadius, float now) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (Ready && !_exploded[index]) { _exploded[index] = true; StopFlight(index); Vector3 val = ((_orbs[index] != null) ? (_positions[index] - position) : Vector3.zero); ReleaseOrb(index); if (((Vector3)(ref val)).sqrMagnitude > 1E-06f) { position += ((Vector3)(ref val)).normalized * 0.3f; } VfxInstance vfxInstance = _pool.Take("forge_plasma_explosion"); vfxInstance.Place(position, Quaternion.identity, Mathf.Max(0.1f, blastRadius / 3f)); vfxInstance.Start(); EnergyLights.Flash(position, in ExplosionLight, now, Mathf.Clamp(blastRadius / 3f, 0.6f, 1.4f)); } } private void Draw(int index, Vector3 position, float radius, bool electric, bool flying, float now) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) _exploded[index] = false; VfxInstance vfxInstance = _orbs[index]; if (vfxInstance != null && _electric[index] != electric) { vfxInstance.Release(); _orbs[index] = null; } bool num = _orbs[index] == null; ref VfxInstance reference = ref _orbs[index]; VfxInstance vfxInstance2 = reference ?? (reference = _pool.Take("forge_plasma_orb", electric ? new Color?(ElectricTint) : ((Color?)null)).Hold()); _electric[index] = electric; _positions[index] = position; float num2 = Mathf.Max(0.02f, radius / 0.3f); vfxInstance2.Place(position, Quaternion.identity, num2); vfxInstance2.Keep(); if (num) { _flying[index] = false; } if (num && !flying) { vfxInstance2.PlayState("charging"); } if (flying && !_flying[index]) { _flying[index] = true; vfxInstance2.PlayState("launch"); if (electric) { vfxInstance2.PlayState("storm"); } EnergyLights.Flash(position, in LaunchLight, now, Mathf.Clamp(num2, 0.5f, 1.2f)); } EnergyLights.Hold(ref _lights[index], position, electric ? ElectricOrbLight : OrbLight, now, Mathf.Clamp(num2, 0.3f, 1.2f)); } public void Pulse(int index, ReadOnlySpan<Vector3> targets, float now) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (Ready) { if (targets.Length < 1 || targets.Length > 3) { throw new ArgumentException("Invalid orb pulse targets."); } Vector3 val = _positions[index]; EnergySound.PlaySpatial(_arcVoices, ref _arcCursor, "shock-pulse", val, 0.22f); EnergyLights.Flash(val, in DischargeLight, now); ReadOnlySpan<Vector3> readOnlySpan = targets; for (int i = 0; i < readOnlySpan.Length; i++) { Vector3 end = readOnlySpan[i]; VfxInstance vfxInstance = _pool.Take("forge_plasma_discharge"); vfxInstance.Stretch(val, end); vfxInstance.Start(); } } } public void Charged(int index) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (Ready) { VfxInstance vfxInstance = _orbs[index]; if (vfxInstance != null) { vfxInstance.PlayState("charged"); EnergyLights.Flash(_positions[index], in ChargedLight, Time.time); } } } private void ReleaseOrb(int index) { _orbs[index]?.Release(); _orbs[index] = null; _flying[index] = false; EnergyLights.Release(ref _lights[index]); } public void Hide(int index) { if (Ready) { StopFlight(index); ReleaseOrb(index); _exploded[index] = false; } } public void Boom(Vector3 position) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (Ready) { EnergySound.PlaySpatial(_impactVoices, ref _impactCursor, "orb-explode", position, 0.48f); } } public void Dispose() { if (_disposed) { return; } _disposed = true; AudioSource[] impactVoices = _impactVoices; foreach (AudioSource val in impactVoices) { if (Alive((Object?)(object)val)) { EnergySound.Stop(val); } } impactVoices = _arcVoices; foreach (AudioSource val2 in impactVoices) { if (Alive((Object?)(object)val2)) { EnergySound.Stop(val2); } } impactVoices = _flightVoices; foreach (AudioSource val3 in impactVoices) { if (Alive((Object?)(object)val3)) { EnergySound.Stop(val3); } } _pool.Dispose(); if (Alive((Object?)(object)_voices)) { Object.Destroy((Object)(object)_voices); } } } internal sealed class PresentationColor { public float R { get; set; } public float G { get; set; } public float B { get; set; } public float A { get; set; } } internal sealed class PartPresentation { public string Name { get; set; } = ""; public string Surface { get; set; } = ""; public string TextureSet { get; set; } = ""; public Vector3 Pivot { get; set; } public PresentationColor Color { get; set; } = new PresentationColor(); } internal sealed class TextureSetPresentation { public string Name { get; set; } = ""; public bool Emission { get; set; } public bool Normal { get; set; } } internal sealed class WeaponPresentation { public string Mode { get; set; } = ""; public Vector3 RightHand { get; set; } public Vector3 LeftHand { get; set; } public Vector3 Muzzle { get; set; } public Vector3 SightLook { get; set; } public Vector3 BoundsMin { get; set; } public Vector3 BoundsMax { get; set; } public PartPresentation[] Parts { get; set; } = Arra