Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Nornforged v0.5.4
BepInEx/plugins/Nornforged/Nornforged.Core.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Rinor")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.4.0")] [assembly: AssemblyInformationalVersion("0.5.4")] [assembly: AssemblyProduct("Nornforged.Core")] [assembly: AssemblyTitle("Nornforged.Core")] [assembly: AssemblyVersion("0.5.4.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 Nornforged.Core { public static class ArsenalRegistry { public static readonly string[] Ids = new string[23] { "flanking_edge", "dueling_edge", "surrounded_edge", "relentless_edge", "grounded_impact", "martial_rhythm", "highground", "giant_hunter", "airborne_hunter", "steady_release", "pinning_shot", "shield_breath", "shield_mending", "shield_rebuke", "shield_composure", "parry_riposte", "wet_conduit", "cinder_hunter", "frozen_focus", "arcane_precision", "spell_repair", "sorcerers_breath", "soul_shelter" }; public static void Add(List<PerkDefinition> target) { M("flanking_edge", "Flanking Edge", PerkClass.Standard, 3.0, 240.0, "+{amount}% direct melee damage while behind the enemy, within the rear 120-degree arc."); M("dueling_edge", "Dueling Edge", PerkClass.Standard, 3.0, 220.0, "+{amount}% direct melee damage when the target is the only living hostile enemy within 6 m of you."); M("surrounded_edge", "Surrounded Edge", PerkClass.Special, 5.0, 420.0, "+{amount}% direct melee damage while at least 3 living hostile enemies are within 8 m of you."); M("relentless_edge", "Relentless Edge", PerkClass.Special, 5.0, 360.0, "+{amount}% direct melee damage against the same enemy you last dealt acknowledged direct damage to, within 3 s. This bonus does not stack with itself."); M("grounded_impact", "Grounded Impact", PerkClass.Standard, 5.0, 300.0, "+{amount}% native melee knockback against a grounded enemy; targets retain their native mass, resistance and knockback rules.", "attack,combat_damage,knockback"); M("martial_rhythm", "Martial Rhythm", PerkClass.Ultra, 5.0, 85.0, "Every third paid manual melee attack that deals acknowledged direct enemy damage refunds {amount}% of that attack's actual stamina cost, capped at 85%. One count and refund per attack, even when it hits several enemies.", "direct_damage,attack_stamina"); R("highground", "High Ground", PerkClass.Standard, 3.0, 240.0, "+{amount}% direct projectile damage when its launch point is at least 3 m above the impact point."); R("giant_hunter", "Giant Hunter", PerkClass.Standard, 3.0, 270.0, "+{amount}% direct projectile damage against an enemy whose maximum health is at least 3 times yours."); R("airborne_hunter", "Airborne Hunter", PerkClass.Special, 5.0, 450.0, "+{amount}% direct projectile damage against an airborne enemy that is neither grounded nor swimming."); R("steady_release", "Steady Release", PerkClass.Special, 5.0, 330.0, "+{amount}% direct projectile damage while above 50% stamina and after at least 8 s without taking health damage; checked at impact."); R("pinning_shot", "Pinning Shot", PerkClass.Standard, 5.0, 240.0, "+{amount}% native stagger buildup from projectile hits at least 15 m from the launch point; no extra health damage.", "stagger_damage,projectile"); C(S("shield_breath", "Shield Breath", PerkClass.Special, 5.0, 85.0, "A successful enemy block refunds {amount}% of stamina actually spent blocking, capped at 85%; {cooldown} s cooldown. The refund occurs after native block success is decided."), 1.0, 20.0); C(S("shield_mending", "Shield Mending", PerkClass.Special, 1.0, 60.0, "A successful enemy block restores {amount}% of this shield's missing durability; {cooldown} s cooldown.", "block,durability"), 3.0, 45.0); C(S("shield_rebuke", "Shield Rebuke", PerkClass.Ultra, 10.0, 600.0, "A successful enemy melee block deals frost damage equal to {amount}% of the physical and elemental damage actually blocked; {cooldown} s cooldown. Native frost resistance applies; no secondary triggers."), 2.0, 30.0); C(S("shield_composure", "Shield Composure", PerkClass.Standard, 5.0, 85.0, "A successful enemy projectile block shortens your remaining stamina-regeneration delay by {amount}%, capped at 85%; {cooldown} s cooldown."), 1.0, 15.0); C(S("parry_riposte", "Parry Riposte", PerkClass.Ultra, 15.0, 750.0, "A successful timed enemy shield block grants +{amount}% direct melee damage for 4 s; {cooldown} s cooldown. The same shield must remain equipped.", "block,parry"), 3.0, 35.0); E("wet_conduit", "Wet Conduit", PerkClass.Standard, 3.0, 240.0, "+{amount}% direct spell damage against an enemy with the native Wet status before impact."); E("cinder_hunter", "Cinder Hunter", PerkClass.Standard, 3.0, 240.0, "+{amount}% direct spell damage against an enemy already affected by native Burning before impact."); E("frozen_focus", "Frozen Focus", PerkClass.Special, 5.0, 360.0, "+{amount}% direct spell damage against an enemy already affected by the native Frost slow before impact."); E("arcane_precision", "Arcane Precision", PerkClass.Special, 5.0, 420.0, "+{amount}% direct spell damage while above 75% health and above 50% Eitr; checked at impact.", "direct_damage,eitr"); C(E("spell_repair", "Spell Repair", PerkClass.Special, 1.0, 60.0, "Acknowledged direct enemy spell damage restores {amount}% of the attacking staff's missing durability; {cooldown} s cooldown.", "direct_damage,durability"), 3.0, 45.0); C(E("sorcerers_breath", "Sorcerer's Breath", PerkClass.Ultra, 5.0, 75.0, "A direct offensive-spell enemy kill restores {amount}% maximum stamina; {cooldown} s cooldown. Damage-over-time and secondary kills do not count."), 3.0, 40.0); C(E("soul_shelter", "Soul Shelter", PerkClass.Ultra, 5.0, 75.0, "A direct offensive-spell enemy kill grants a barrier worth {amount}% maximum health for 6 s; {cooldown} s cooldown. Barriers take the strongest remaining amount; damage-over-time and secondary kills do not count."), 5.0, 60.0); static void C(PerkDefinition p, double min, double max) { p.Parameters.Add(new PerkParameter { Id = "cooldown", Min = min, Max = max, Unit = "s", Precision = 3, HigherIsBetter = false }); } PerkDefinition D(string id, string name, PerkClass cls, double min, double max, string trigger, string categories, string caps) { PerkDefinition perkDefinition = new PerkDefinition { Id = id, Name = name, Effect = id, Class = cls, Categories = categories.Split(',').ToList(), RequiredCapabilities = caps.Split(',').ToList(), Parameters = new List<PerkParameter> { new PerkParameter { Id = "amount", Min = min, Max = max, Precision = 3, Unit = "%" } }, Trigger = trigger, Stacking = "Conditional damage bonuses add before one multiplier and keep separate actual-damage attribution. Item-only effects use the attacking weapon or the shield that actually blocked. Character-wide cooldowns survive equipment swaps.", SafetyLimits = "Enemy direct hits only; secondary and reflected hits cannot start bonuses or rewards. Resource refunds require confirmed damage or actual successful blocks. Native resistance, armor, blocking, ownership and resource maxima remain." }; target.Add(perkDefinition); return perkDefinition; } PerkDefinition E(string id, string name, PerkClass cls, double min, double max, string text, string caps = "direct_damage") { return D(id, name, cls, min, max, text, "offensive_staff", caps); } PerkDefinition M(string id, string name, PerkClass cls, double min, double max, string text, string caps = "direct_damage") { return D(id, name, cls, min, max, text, "melee", caps); } PerkDefinition R(string id, string name, PerkClass cls, double min, double max, string text, string caps = "direct_damage,projectile") { return D(id, name, cls, min, max, text, "bow,crossbow", caps); } PerkDefinition S(string id, string name, PerkClass cls, double min, double max, string text, string caps = "block") { return D(id, name, cls, min, max, text, "shield", caps); } } } public sealed class AuthorityStorage { private sealed class KeyMaterial { public readonly string Xml; public readonly string PublicXml; public readonly string Fingerprint; public KeyMaterial(string xml, string publicXml, string fingerprint) { Xml = xml; PublicXml = publicXml; Fingerprint = fingerprint; } } public const string KeyFileName = "authority-private-key.xml"; public const string IdentityFileName = "authority-identity.json"; private const int MaximumFileBytes = 16777216; private const int MaximumStartupValidationEntries = 65536; [ThreadStatic] private static HashSet<string>? startupDirectories; private readonly string backupDirectory; private readonly object gate = new object(); private Dictionary<string, bool>? startupValidation; public string DirectoryPath { get; } public string Fingerprint { get; } public string PrivateKeyXml { get; } public string StatusMessage { get; private set; } private AuthorityStorage(string directory, string backup, KeyMaterial key, string status) { DirectoryPath = directory; backupDirectory = backup; Fingerprint = key.Fingerprint; PrivateKeyXml = key.Xml; StatusMessage = status; } public static AuthorityStorage Open(string dataDirectory, string backupDirectory, string legacyDirectory) { HashSet<string> hashSet = startupDirectories; startupDirectories = new HashSet<string>((Path.DirectorySeparatorChar == '\\') ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); try { return OpenCore(dataDirectory, backupDirectory, legacyDirectory); } finally { startupDirectories = hashSet; } } private static AuthorityStorage OpenCore(string dataDirectory, string backupDirectory, string legacyDirectory) { string text = Root(dataDirectory); string text2 = Root(backupDirectory); string text3 = Root(legacyDirectory); if (Overlaps(text, text2) || Overlaps(text, text3) || Overlaps(text2, text3)) { throw new ArgumentException("Authority data, backup and legacy directories must be separate, non-nested directories."); } string text4 = ReadWitness(text); string text5 = ReadWitness(text2); if (text4 != null && text5 != null && text4 != text5) { throw Failure("The data and backup identity witnesses disagree. Preserve both directories and restore the original matching authority backup while the server is stopped."); } string text6 = text4 ?? text5; KeyMaterial keyMaterial = ReadKey(SafePath(text, "authority-private-key.xml")); KeyMaterial keyMaterial2 = ReadKey(SafePath(text2, "authority-private-key.xml")); List<KeyMaterial> list = SavedKeys(text2).ToList(); if (keyMaterial2 != null) { list.Add(keyMaterial2); } if (list.Select((KeyMaterial k) => k.Fingerprint).Distinct<string>(StringComparer.Ordinal).Count() > 1) { throw Failure("The backup contains different signing identities. No identity was selected automatically. Preserve all keys and restore a matching data/backup pair offline."); } keyMaterial2 = keyMaterial2 ?? list.FirstOrDefault(); if (keyMaterial != null && text6 != null && keyMaterial.Fingerprint != text6) { throw Failure("The primary signing key does not match the saved server identity. No key was overwritten. Restore the matching original key offline."); } if (keyMaterial2 != null && text6 != null && keyMaterial2.Fingerprint != text6) { throw Failure("The backup signing key does not match the saved server identity. No key was overwritten. Restore a matching original backup offline."); } if (keyMaterial != null && keyMaterial2 != null && keyMaterial.Fingerprint != keyMaterial2.Fingerprint) { throw Failure("The primary and backup signing keys disagree. Preserve both keys and restore a matching authority backup offline."); } KeyMaterial keyMaterial3 = ReadKey(SafePath(text3, "authority-private-key.xml")); KeyMaterial keyMaterial4 = keyMaterial ?? keyMaterial2; bool flag = false; bool flag2 = keyMaterial == null && keyMaterial2 != null; bool flag3 = false; if (keyMaterial4 == null && keyMaterial3 != null) { if (text6 != null && keyMaterial3.Fingerprint != text6) { throw Failure("The legacy config key does not match the saved server identity. The original signing key is required; no replacement was generated."); } if (text6 == null && HasEvidence(text)) { throw Failure("Authority data exists but its identity cannot be verified against the legacy config key. Restore the original key and matching backup offline."); } keyMaterial4 = keyMaterial3; flag = true; } if (keyMaterial4 == null) { if (HasEvidence(text) || HasEvidence(text2) || HasEvidence(text3)) { throw Failure("An existing authority was detected but its signing key is missing or invalid. Existing item metadata is preserved. Restore the original authority backup; a new key cannot recover old signatures."); } using (RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider(2048) { PersistKeyInCsp = false }) { keyMaterial4 = ParseKey(rSACryptoServiceProvider.ToXmlString(includePrivateParameters: true)); } flag3 = true; } if (text6 != null && keyMaterial4.Fingerprint != text6) { throw Failure("The signing key does not match the saved server identity. Restore the original key offline."); } string text7 = (flag3 ? "New authority identity created and backed up outside the config folder." : (flag ? "Existing authority key and records migrated outside the config folder; the legacy originals were preserved." : (flag2 ? "Original authority signing key restored automatically from its matching independent backup." : "Authority identity verified; independent backups are available."))); if (keyMaterial3 != null && keyMaterial3.Fingerprint != keyMaterial4.Fingerprint) { text7 += " The different legacy config key was ignored and preserved; this server continues using its established identity."; } AuthorityStorage authorityStorage = new AuthorityStorage(text, text2, keyMaterial4, text7); authorityStorage.startupValidation = new Dictionary<string, bool>(StringComparer.Ordinal); try { List<string> list2 = new List<string> { text2, text }; if (keyMaterial3 != null && keyMaterial3.Fingerprint == keyMaterial4.Fingerprint) { list2.Insert(0, text3); } Dictionary<string, string> dictionary = authorityStorage.CollectRecords(list2); Directory.CreateDirectory(text); Directory.CreateDirectory(text2); WriteImmutable(SafePath(text2, Path.Combine("keys", keyMaterial4.Fingerprint + ".xml")), keyMaterial4.Xml); authorityStorage.SaveKeyAndWitness(text2, keyMaterial4); authorityStorage.SaveKeyAndWitness(text, keyMaterial4); foreach (KeyValuePair<string, string> item in dictionary) { authorityStorage.RestoreRecord(text, item.Key, item.Value); authorityStorage.BackupFile(item.Key); } return authorityStorage; } finally { authorityStorage.startupValidation = null; } } public void BackupFile(string relativePath) { lock (gate) { ValidateRecordPath(relativePath); string text = ReadText(SafePath(DirectoryPath, relativePath)); if (!ValidRecord(relativePath, text)) { throw Failure("An authority record is invalid; its original file was preserved and no backup was overwritten."); } string path = SafePath(backupDirectory, relativePath); if (File.Exists(path)) { string text2 = ReadText(path); if (text2 == text) { return; } if (ValidRecord(relativePath, text2)) { if (SelectRecord(relativePath, text2, text) != text) { throw Failure("A committed authority backup cannot be replaced by an older prepared transaction."); } Archive(relativePath, text2); } else { Quarantine(path, backupDirectory); } } Archive(relativePath, text); AtomicWrite(path, text); } } private Dictionary<string, string> CollectRecords(IEnumerable<string> roots) { Dictionary<string, string> result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string root in roots) { foreach (string item in RecordPaths(root)) { string text = ReadText(SafePath(root, item)); if (!ValidRecord(item, text)) { hashSet.Add(item); } else { result[item] = (result.TryGetValue(item, out string value) ? SelectRecord(item, value, text) : text); } } } foreach (KeyValuePair<string, string> item2 in HistoryRecords()) { if (!ValidRecord(item2.Key, item2.Value)) { hashSet.Add(item2.Key); } else { result[item2.Key] = (result.TryGetValue(item2.Key, out string value2) ? SelectRecord(item2.Key, value2, item2.Value) : item2.Value); } } if (hashSet.Any((string path) => !result.ContainsKey(path))) { throw Failure("An authority receipt or configuration snapshot is invalid and has no valid matching backup. Originals were preserved; restore a matching authority backup offline."); } foreach (KeyValuePair<string, string> item3 in result.Where<KeyValuePair<string, string>>((KeyValuePair<string, string> pair) => !pair.Key.Replace('\\', '/').StartsWith("configurations/", StringComparison.Ordinal))) { string text2 = ((JToken)JObject.Parse(item3.Value)).Value<string>((object)"Config") ?? ""; if (IsHash(text2) && !result.ContainsKey(Path.Combine("configurations", text2 + ".json"))) { throw Failure("A saved craft refers to a missing configuration snapshot. Restore the complete original authority backup, including its configurations directory."); } } return result; } private IEnumerable<KeyValuePair<string, string>> HistoryRecords() { string path = SafePath(backupDirectory, "history"); if (!Directory.Exists(path)) { yield break; } string[] directories = Directory.GetDirectories(path); foreach (string path2 in directories) { AssertNoLinks(path2); string group = Path.GetFileName(path2); if (group != "configurations" && !long.TryParse(group, out var _)) { continue; } string[] directories2 = Directory.GetDirectories(path2); foreach (string path3 in directories2) { AssertNoLinks(path3); string relative = Path.Combine(group, Path.GetFileName(path3)); ValidateRecordPath(relative); string[] files = Directory.GetFiles(path3); foreach (string text in files) { AssertNoLinks(text); if (text.EndsWith(".bak", StringComparison.Ordinal)) { string text2 = ReadText(text); if (Hash(text2) != Path.GetFileNameWithoutExtension(text)) { throw Failure("An immutable authority record archive failed its checksum. Preserve the backup and restore its matching original copy offline."); } yield return new KeyValuePair<string, string>(relative, text2); } } } } } private string SelectRecord(string relative, string first, string second) { if (first == second) { return first; } JObject val = JObject.Parse(first); JObject val2 = JObject.Parse(second); if (JToken.DeepEquals((JToken)(object)val, (JToken)(object)val2)) { return second; } if (relative.Replace('\\', '/').StartsWith("configurations/", StringComparison.Ordinal)) { throw Failure("Different configuration snapshots share the same identity; originals were preserved."); } bool valueOrDefault = ((JToken)val).Value<bool?>((object)"Committed") == true; bool valueOrDefault2 = ((JToken)val2).Value<bool?>((object)"Committed") == true; if (valueOrDefault != valueOrDefault2) { val.Remove("Committed"); val2.Remove("Committed"); val.Remove("Items"); val2.Remove("Items"); if (JToken.DeepEquals((JToken)(object)val, (JToken)(object)val2)) { if (!valueOrDefault) { return second; } return first; } } throw Failure("Different authority records share a transaction ID. No record was overwritten; restore the matching original receipt offline."); } private bool ValidRecord(string relative, string text) { Dictionary<string, bool> dictionary = startupValidation; if (dictionary == null) { return ValidateRecordContents(relative, text); } string key = relative + "\n" + Hash(text); if (dictionary.TryGetValue(key, out var value)) { return value; } value = ValidateRecordContents(relative, text); if (dictionary.Count < 65536) { dictionary.Add(key, value); } return value; } private bool ValidateRecordContents(string relative, string text) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Invalid comparison between Unknown and I4 //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Invalid comparison between Unknown and I4 //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Invalid comparison between Unknown and I4 //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Invalid comparison between Unknown and I4 //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Invalid comparison between Unknown and I4 //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Invalid comparison between Unknown and I4 //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Invalid comparison between Unknown and I4 try { if (relative.Replace('\\', '/').StartsWith("configurations/", StringComparison.Ordinal)) { return Hash(text) == Path.GetFileNameWithoutExtension(relative) && JObject.Parse(text) != null; } JObject val = JObject.Parse(text); JToken obj = val["Transaction"]; if (obj == null || (int)obj.Type != 8) { return false; } if (((JToken)val).Value<string>((object)"Transaction") != Path.GetFileNameWithoutExtension(relative)) { return false; } if (val["Config"] != null && (int)val["Config"].Type != 8) { return false; } JToken obj2 = val["Committed"]; if (obj2 != null && (int)obj2.Type == 9) { JToken obj3 = val["Items"]; JArray val2 = (JArray)(object)((obj3 is JArray) ? obj3 : null); if (val2 != null) { JToken val3 = val["OutputExclusion"]; bool flag = val3 != null && (int)val3.Type != 10; if (((JToken)val).Value<bool>((object)"Committed")) { JToken obj4 = val["Prefab"]; if (obj4 == null || (int)obj4.Type != 8) { return false; } if (((JContainer)val2).Count > 128 || (((JContainer)val2).Count != 0 && flag)) { return false; } using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider { PersistKeyInCsp = false }; rSACryptoServiceProvider.FromXmlString(PrivateKeyXml); if (((JContainer)val2).Count == 0) { if (!flag || !ValidSignedEnvelope(val3, ((JToken)val).Value<string>((object)"Prefab"), rSACryptoServiceProvider)) { return false; } JObject val4 = JObject.Parse(val3.Value<string>((object)"Json")); JToken obj5 = val4["Kind"]; if (obj5 == null || (int)obj5.Type != 8 || ((JToken)val4).Value<string>((object)"Kind") != "processor-exclusion-v1") { return false; } string[] array = new string[4] { "Transaction", "Prefab", "Host", "Recipe" }; int num = 0; while (num < array.Length) { string text2 = array[num]; JToken obj6 = val[text2]; if (obj6 != null && (int)obj6.Type == 8) { JToken obj7 = val4[text2]; if (obj7 != null && (int)obj7.Type == 8 && !(((JToken)val).Value<string>((object)text2) != ((JToken)val4).Value<string>((object)text2))) { num++; continue; } } return false; } if (!((JToken)val).Value<string>((object)"Host").StartsWith("processor:", StringComparison.Ordinal) || !((JToken)val).Value<string>((object)"Recipe").StartsWith("processor:piece_FrostFoundry:", StringComparison.Ordinal)) { return false; } } foreach (JToken item in val2) { if (!ValidSignedEnvelope(item, ((JToken)val).Value<string>((object)"Prefab"), rSACryptoServiceProvider)) { return false; } } } else if (((JContainer)val2).Count != 0 || flag) { return false; } return true; } } return false; } catch (Exception ex) when (ex is JsonException || ex is FormatException || ex is CryptographicException || ex is ArgumentException || ex is InvalidOperationException || ex is OverflowException) { return false; } } private bool ValidSignedEnvelope(JToken envelope, string prefab, RSACryptoServiceProvider rsa) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 if (envelope is JObject) { JToken obj = envelope[(object)"Issuer"]; if (obj != null && (int)obj.Type == 8) { JToken obj2 = envelope[(object)"Json"]; if (obj2 != null && (int)obj2.Type == 8) { JToken obj3 = envelope[(object)"Signature"]; if (obj3 != null && (int)obj3.Type == 8) { if (envelope.Value<string>((object)"Issuer") != Fingerprint) { return false; } string s = prefab + "\n" + envelope.Value<string>((object)"Json"); return rsa.VerifyData(Encoding.UTF8.GetBytes(s), "2.16.840.1.101.3.4.2.1", Convert.FromBase64String(envelope.Value<string>((object)"Signature") ?? "")); } } } } return false; } private void RestoreRecord(string root, string relative, string contents) { string path = SafePath(root, relative); if (File.Exists(path)) { string text = ReadText(path); if (text == contents) { return; } if (!ValidRecord(relative, text)) { Quarantine(path, root); } else { Archive(relative, text); } } AtomicWrite(path, contents); } private void Archive(string relative, string contents) { WriteImmutable(SafePath(backupDirectory, Path.Combine("history", relative, Hash(contents) + ".bak")), contents); } private void SaveKeyAndWitness(string root, KeyMaterial key) { string path = SafePath(root, "authority-private-key.xml"); if (File.Exists(path)) { KeyMaterial keyMaterial = ReadKey(path); if (keyMaterial == null) { Quarantine(path, root); AtomicWrite(path, key.Xml); } else if (keyMaterial.Fingerprint != key.Fingerprint) { throw Failure("Refusing to replace a different signing key."); } } if (!File.Exists(path)) { AtomicWrite(path, key.Xml); } string path2 = SafePath(root, "authority-identity.json"); string contents = JsonConvert.SerializeObject((object)new { Schema = 1, Fingerprint = key.Fingerprint, PublicKey = key.PublicXml }, (Formatting)1); if (File.Exists(path2) && ReadWitness(root) == null) { Quarantine(path2, root); AtomicWrite(path2, contents); } if (!File.Exists(path2)) { AtomicWrite(path2, contents); } } private static IEnumerable<string> RecordPaths(string root) { if (!Directory.Exists(root)) { yield break; } string[] directories = Directory.GetDirectories(root); foreach (string path in directories) { AssertNoLinks(path); string name = Path.GetFileName(path); if (name != "configurations" && !long.TryParse(name, out var _)) { continue; } string[] files = Directory.GetFiles(path); foreach (string text in files) { AssertNoLinks(text); if (text.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { string text2 = Path.Combine(name, Path.GetFileName(text)); ValidateRecordPath(text2); yield return text2; } } } } private static void ValidateRecordPath(string relative) { if (string.IsNullOrWhiteSpace(relative) || Path.IsPathRooted(relative)) { throw new ArgumentException("A relative authority record path is required."); } string[] array = relative.Replace('\\', '/').Split('/'); if (array.Length != 2 || array.Any(delegate(string p) { switch (p) { default: return p.IndexOf(':') >= 0; case "": case ".": case "..": return true; } }) || !array[1].EndsWith(".json", StringComparison.Ordinal)) { throw new ArgumentException("Only world transaction JSON and configuration snapshots may be backed up."); } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(array[1]); long result; Guid result2; if (array[0] == "configurations") { if (!IsHash(fileNameWithoutExtension)) { throw new ArgumentException("Configuration snapshots require their SHA-256 filename."); } } else if (!long.TryParse(array[0], out result) || !Guid.TryParseExact(fileNameWithoutExtension, "N", out result2)) { throw new ArgumentException("A valid world and transaction identifier is required."); } } private static IEnumerable<KeyMaterial> SavedKeys(string root) { string path = SafePath(root, "keys"); if (!Directory.Exists(path)) { yield break; } string[] files = Directory.GetFiles(path); foreach (string text in files) { AssertNoLinks(text); if (text.EndsWith(".xml", StringComparison.Ordinal)) { KeyMaterial keyMaterial = ReadKey(text); if (keyMaterial == null || Path.GetFileNameWithoutExtension(text) != keyMaterial.Fingerprint) { throw Failure("An immutable authority key backup is invalid. Preserve it and restore the matching original backup offline."); } yield return keyMaterial; } } } private static string? ReadWitness(string root) { string path = SafePath(root, "authority-identity.json"); if (!File.Exists(path)) { return null; } try { JObject obj = JObject.Parse(ReadText(path)); string text = ((JToken)obj).Value<string>((object)"Fingerprint"); string text2 = ((JToken)obj).Value<string>((object)"PublicKey"); if (((JToken)obj).Value<int?>((object)"Schema") != 1 || !IsHash(text) || string.IsNullOrWhiteSpace(text2)) { return null; } using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider { PersistKeyInCsp = false }; rSACryptoServiceProvider.FromXmlString(text2); return (Hash(rSACryptoServiceProvider.ToXmlString(includePrivateParameters: false)) == text) ? text : null; } catch (Exception ex) when (ex is JsonException || ex is CryptographicException || ex is ArgumentException || ex is FormatException || ex is XmlException || ex.GetType().FullName == "System.Security.XmlSyntaxException") { return null; } } private static KeyMaterial? ReadKey(string path) { if (!File.Exists(path)) { return null; } return ParseKey(ReadText(path)); } private static KeyMaterial? ParseKey(string xml) { try { using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider { PersistKeyInCsp = false }; rSACryptoServiceProvider.FromXmlString(xml); if (rSACryptoServiceProvider.PublicOnly || rSACryptoServiceProvider.KeySize < 2048) { return null; } byte[] bytes = Encoding.UTF8.GetBytes("Nornforged authority key check"); if (!rSACryptoServiceProvider.VerifyData(bytes, "2.16.840.1.101.3.4.2.1", rSACryptoServiceProvider.SignData(bytes, "2.16.840.1.101.3.4.2.1"))) { return null; } string text = rSACryptoServiceProvider.ToXmlString(includePrivateParameters: false); return new KeyMaterial(xml, text, Hash(text)); } catch (Exception ex) when (ex is CryptographicException || ex is ArgumentException || ex is FormatException || ex is XmlException || ex.GetType().FullName == "System.Security.XmlSyntaxException") { return null; } } private static bool HasEvidence(string root) { if (Directory.Exists(root)) { return Directory.EnumerateFileSystemEntries(root).Any(); } return false; } private static bool IsHash(string? value) { if (value != null && value.Length == 64) { return value.All((char c) => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')); } return false; } private static string Hash(string text) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "").ToLowerInvariant(); } private static InvalidOperationException Failure(string message) { return new InvalidOperationException("Nornforged authority recovery required: " + message); } private static string Root(string path) { if (string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path)) { throw new ArgumentException("Authority directories must use absolute paths."); } if (Path.DirectorySeparatorChar == '\\') { path = path.Replace('/', '\\'); } string text = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (string.Equals(text, Path.GetPathRoot(text)?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("A filesystem root cannot be an authority directory."); } if (Path.DirectorySeparatorChar == '\\' && !text.StartsWith("\\\\?\\", StringComparison.Ordinal)) { text = (text.StartsWith("\\\\", StringComparison.Ordinal) ? ("\\\\?\\UNC\\" + text.Substring(2)) : ("\\\\?\\" + text)); } AssertNoLinks(text); return text; } private static bool Overlaps(string first, string second) { if (!string.Equals(first, second, StringComparison.OrdinalIgnoreCase) && !first.StartsWith(second + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { return second.StartsWith(first + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); } return true; } private static string SafePath(string root, string relative) { if (Path.IsPathRooted(relative) || relative.Replace('\\', '/').Split('/').Any((string s) => s == ".." || s == "." || s.IndexOf(':') >= 0)) { throw new ArgumentException("Unsafe authority path."); } relative = relative.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); string fullPath = Path.GetFullPath(Path.Combine(root, relative)); if (!fullPath.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Authority path escaped its directory."); } AssertNoLinks(fullPath); return fullPath; } private static void AssertNoLinks(string path) { HashSet<string> hashSet = startupDirectories; List<string> list = ((hashSet != null) ? new List<string>() : null); int num = 0; for (string text = path; !string.IsNullOrEmpty(text); text = ParentPath(text)) { if (++num > 1024) { throw new IOException("Authority directory nesting exceeds the supported limit."); } if (hashSet != null && hashSet.Contains(text)) { break; } FileAttributes attributes; try { attributes = File.GetAttributes(text); } catch (FileNotFoundException) { continue; } catch (DirectoryNotFoundException) { continue; } if ((attributes & FileAttributes.ReparsePoint) != FileAttributes.None) { throw new IOException("Authority data paths cannot contain symbolic links or junctions."); } if ((attributes & FileAttributes.Directory) != FileAttributes.None) { list?.Add(text); } } if (hashSet == null || list == null) { return; } foreach (string item in list) { if (hashSet.Count >= 65536) { break; } hashSet.Add(item); } } private static string? ParentPath(string path) { string directoryName = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(directoryName) || string.Equals(path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), directoryName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), (Path.DirectorySeparatorChar == '\\') ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) { return null; } return directoryName; } private static string ReadText(string path) { AssertNoLinks(path); if (new FileInfo(path).Length > 16777216) { throw Failure("Authority file exceeds the supported size; its original content was preserved."); } return File.ReadAllText(path, Encoding.UTF8); } private static void Quarantine(string path, string root) { string text = ReadText(path); string path2 = SafePath(root, Path.Combine("quarantine", Path.GetFileName(path) + "." + Hash(text) + ".invalid")); WriteImmutable(path2, text); if (ReadText(path2) != text) { throw new IOException("Failed to preserve invalid authority data."); } } private static void WriteImmutable(string path, string contents) { if (File.Exists(path)) { if (ReadText(path) != contents) { throw Failure("An immutable authority archive already exists with different contents; it was not overwritten."); } } else { AtomicWrite(path, contents, replaceExisting: false); } } private static void AtomicWrite(string path, string contents, bool replaceExisting = true) { AssertNoLinks(path); Directory.CreateDirectory(Path.GetDirectoryName(path)); string text = Path.Combine(Path.GetDirectoryName(path), ".nf-" + Guid.NewGuid().ToString("N") + ".tmp"); using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough)) { using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(contents); streamWriter.Flush(); fileStream.Flush(flushToDisk: true); } if (replaceExisting && File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } } public static class Balance020 { public const string Version = "0.2.0"; public const int GameplayPrecision = 3; public static void Apply(IReadOnlyList<PerkDefinition> definitions) { Dictionary<string, (double, double)> dictionary = Ranges(); int num = 0; foreach (PerkDefinition definition in definitions) { foreach (PerkParameter parameter in definition.Parameters) { string text = definition.Id + "." + parameter.Id; if (!dictionary.TryGetValue(text, out var value)) { throw new InvalidOperationException("Missing reviewed 0.2.0 balance range: " + text); } parameter.Min = value.Item1; parameter.Max = value.Item2; parameter.Precision = 3; num++; } } if (num != dictionary.Count) { throw new InvalidOperationException("The 0.2.0 balance table contains an unused parameter range."); } } public static Dictionary<string, (double Min, double Max)> Ranges() { Dictionary<string, (double, double)> result = new Dictionary<string, (double, double)>(StringComparer.Ordinal); A("razor_edge", 1.0, 50.0); A("runic_potency", 1.0, 48.0); A("efficient_strikes", 1.0, 38.0); A("quickstrike", 0.5, 25.0); A("quick_draw", 1.0, 35.0); A("rapid_reload", 1.0, 35.0); A("steady_aim", 1.0, 38.0); A("eitr_efficiency", 0.5, 35.0); A("punishing_blows", 1.0, 55.0); A("crushing_force", 1.0, 50.0); A("duelists_guard", 1.0, 50.0); A("reinforced", 3.0, 220.0); A("lightweight", 1.0, 75.0); A("frostbound", 0.5, 28.0); A("emberbound", 0.5, 28.0); A("resource_breaker", 1.0, 65.0); A("efficient_labor", 1.0, 38.0); A("swift_labor", 0.5, 30.0); A("resource_magnet", 2.0, 180.0); A("extended_reach", 1.0, 55.0); A("haulers_strength", 1.0, 35.0); A("builders_reach", 1.0, 70.0); A("green_hands", 1.0, 45.0); A("patient_angler", 1.0, 45.0); A("long_cast", 1.0, 55.0); A("fortified", 0.5, 30.0); A("vitality", 0.5, 22.0); A("endurance", 0.5, 25.0); A("runic_reservoir", 1.0, 30.0); A("recovery", 1.0, 50.0); A("second_wind", 1.0, 50.0); A("runic_flow", 1.0, 50.0); A("fleetfoot", 0.25, 12.0); A("tireless", 1.0, 32.0); A("nimble", 1.0, 32.0); A("springstep", 1.0, 32.0); A("riverstride", 1.0, 32.0); A("quiet_steps", 1.0, 32.0); string[] array = new string[8] { "fire", "frost", "lightning", "poison", "spirit", "blunt", "slash", "pierce" }; foreach (string text in array) { A("ward_" + text, 0.5, (text == "fire" || text == "frost") ? 20 : 16); } A("soft_landing", 2.0, 75.0); A("efficient_guard", 1.0, 38.0); A("firm_grip", 1.0, 60.0); A("stormspark", 5.0, 100.0); C("stormspark", 3.0, 24.0); A("executioner", 3.0, 65.0); A("runic_reserve", 3.0, 65.0); A("veinbreaker", 10.0, 220.0); A("natures_favor", 1.0, 65.0); A("master_repair", 1.0, 9.0); C("master_repair", 1.0, 20.0); A("emergency_ward", 2.0, 35.0); C("emergency_ward", 20.0, 240.0); A("windstep", 1.0, 30.0); C("windstep", 3.0, 45.0); A("runic_guard", 2.0, 30.0); C("runic_guard", 5.0, 90.0); A("second_breath", 2.0, 35.0); C("second_breath", 15.0, 180.0); A("calm_heart", 3.0, 70.0); A("patient_recovery", 3.0, 70.0); A("deep_breath", 3.0, 70.0); A("steady_foothold", 3.0, 60.0); C("steady_foothold", 4.0, 75.0); A("travelers_vigor", 3.0, 60.0); A("thors_judgment", 20.0, 650.0); C("thors_judgment", 2.5, 40.0); A("fenrirs_hunger", 0.5, 18.0); A("fatebreaker", 10.0, 550.0); P("fatebreaker", "boss", 3.0, 200.0); C("fatebreaker", 2.0, 60.0); A("phantom_strike", 10.0, 500.0); P("phantom_strike", "chance", 2.0, 65.0); C("phantom_strike", 1.0, 35.0); A("winter_sovereign", 15.0, 600.0); C("winter_sovereign", 3.0, 65.0); A("runic_echo", 10.0, 450.0); C("runic_echo", 2.0, 50.0); A("ymirs_awakening", 50.0, 900.0); C("ymirs_awakening", 3.0, 75.0); A("earths_bounty", 5.0, 225.0); A("eternal_temper", 25.0, 99.0); A("tireless_creator", 5.0, 95.0); P("tireless_creator", "chance", 2.0, 80.0); A("runic_momentum", 2.0, 80.0); C("runic_momentum", 3.0, 75.0); A("worldsmith", 2.0, 20.0); C("worldsmith", 1.0, 45.0); A("freyrs_garden", 5.0, 80.0); A("aegirs_favor", 2.0, 90.0); A("yggdrasils_shelter", 5.0, 90.0); C("yggdrasils_shelter", 10.0, 240.0); A("bifrost_stride", 3.0, 60.0); P("bifrost_stride", "reduction", 5.0, 85.0); C("bifrost_stride", 1.0, 45.0); A("einherjars_return", 5.0, 90.0); C("einherjars_return", 600.0, 3600.0); A("heimdalls_bastion", 5.0, 90.0); C("heimdalls_bastion", 5.0, 120.0); A("guardians_wrath", 10.0, 450.0); C("guardians_wrath", 1.5, 40.0); A("runic_rebound", 10.0, 500.0); C("runic_rebound", 2.0, 60.0); A("fates_renewal", 5.0, 90.0); C("fates_renewal", 5.0, 120.0); A("ancient_resolve", 2.0, 70.0); C("ancient_resolve", 5.0, 120.0); A("travelers_mercy", 0.2, 3.0); C("travelers_mercy", 0.75, 60.0); A("norns_respite", 5.0, 350.0); A("undying_tenacity", 2.0, 70.0); A("dulled", 1.0, 32.0); A("faded_runes", 1.0, 32.0); A("exhausting", 1.0, 35.0); A("sluggish", 0.25, 15.0); A("brittle", 3.0, 65.0); A("heavy", 2.0, 100.0); A("restrictive", 0.25, 8.0); A("poor_guard", 1.0, 35.0); A("thin_plating", 1.0, 25.0); A("draining", 0.25, 15.0); A("frail", 0.25, 15.0); A("tiring", 1.0, 25.0); A("unbalanced", 1.0, 45.0); A("short_reach", 1.0, 30.0); A("slow_mending", 1.0, 25.0); A("burdened", 1.0, 25.0); A("vulnerable", 0.5, 18.0); A("runic_leak", 1.0, 35.0); return result; void A(string id, double min, double max) { result.Add(id + ".amount", (min, max)); } void C(string id, double min, double max) { P(id, "cooldown", min, max); } void P(string id, string parameter, double min, double max) { result.Add(id + "." + parameter, (min, max)); } } } public static class Balance021 { public const string Version = "0.2.1"; public const int GameplayPrecision = 3; private static readonly HashSet<string> HighRankParameters; static Balance021() { HighRankParameters = new HashSet<string>(StringComparer.Ordinal); string[] array = "razor_edge,runic_potency,punishing_blows,crushing_force,duelists_guard,reinforced,frostbound,emberbound,resource_breaker,extended_reach,haulers_strength,builders_reach,long_cast,fortified,vitality,endurance,runic_reservoir,recovery,second_wind,runic_flow,stormspark,executioner,veinbreaker,emergency_ward,runic_guard,calm_heart,patient_recovery,deep_breath,travelers_vigor,thors_judgment,fatebreaker,phantom_strike,winter_sovereign,runic_echo,ymirs_awakening,yggdrasils_shelter,heimdalls_bastion,guardians_wrath,runic_rebound,norns_respite,work_breath,work_reserve,steady_supplies,breath_of_hearth,guard_focus,storm_step,opening_cut,desperate_edge,farshot,point_blank,ambushers_edge,retaliation_edge,guardbreaker,spellbattery,followthrough,tyrs_measure,bloodstar,flanking_edge,dueling_edge,surrounded_edge,relentless_edge,grounded_impact,highground,giant_hunter,airborne_hunter,steady_release,pinning_shot,shield_rebuke,parry_riposte,wet_conduit,cinder_hunter,frozen_focus,arcane_precision,soul_shelter".Split(','); foreach (string text in array) { HighRankParameters.Add(text + ".amount"); } HighRankParameters.Add("fatebreaker.boss"); } public static bool AllowsHighRankMaximum(string definitionId, string parameterId) { return HighRankParameters.Contains(definitionId + "." + parameterId); } public static void Apply(IReadOnlyList<PerkDefinition> definitions) { Dictionary<string, double> dictionary = ReviewedMaxima(); int num = 0; foreach (PerkDefinition definition in definitions) { foreach (PerkParameter parameter in definition.Parameters) { if (dictionary.TryGetValue(definition.Id + "." + parameter.Id, out var value)) { if (parameter.Unit != "%" || value < parameter.Max) { throw new InvalidOperationException("Invalid reviewed 0.2.1 percentage maximum: " + definition.Id + "." + parameter.Id); } parameter.Max = value; num++; } parameter.Precision = 3; if (AllowsHighRankMaximum(definition.Id, parameter.Id)) { if (definition.Class == PerkClass.Negative || parameter.Unit != "%" || !parameter.HigherIsBetter) { throw new InvalidOperationException("Invalid high-rank percentage contract: " + definition.Id + "." + parameter.Id); } parameter.HighRankMaxima = new Dictionary<int, double> { [9] = parameter.Max * 1.5, [10] = parameter.Max * 2.0, [11] = parameter.Max * 2.5, [12] = parameter.Max * 3.0 }; } } } if (num != dictionary.Count) { throw new InvalidOperationException("The 0.2.1 balance table contains an unused parameter maximum."); } } public static Dictionary<string, double> ReviewedMaxima() { return new Dictionary<string, double>(StringComparer.Ordinal) { ["razor_edge.amount"] = 150.0, ["runic_potency.amount"] = 144.0, ["efficient_strikes.amount"] = 85.0, ["quickstrike.amount"] = 75.0, ["quick_draw.amount"] = 90.0, ["rapid_reload.amount"] = 85.0, ["steady_aim.amount"] = 85.0, ["eitr_efficiency.amount"] = 85.0, ["punishing_blows.amount"] = 165.0, ["crushing_force.amount"] = 150.0, ["duelists_guard.amount"] = 150.0, ["reinforced.amount"] = 660.0, ["lightweight.amount"] = 80.0, ["frostbound.amount"] = 84.0, ["emberbound.amount"] = 84.0, ["resource_breaker.amount"] = 195.0, ["efficient_labor.amount"] = 85.0, ["swift_labor.amount"] = 90.0, ["resource_magnet.amount"] = 540.0, ["extended_reach.amount"] = 165.0, ["haulers_strength.amount"] = 105.0, ["builders_reach.amount"] = 210.0, ["green_hands.amount"] = 85.0, ["patient_angler.amount"] = 85.0, ["long_cast.amount"] = 165.0, ["fortified.amount"] = 90.0, ["vitality.amount"] = 66.0, ["endurance.amount"] = 75.0, ["runic_reservoir.amount"] = 90.0, ["recovery.amount"] = 150.0, ["second_wind.amount"] = 150.0, ["runic_flow.amount"] = 150.0, ["fleetfoot.amount"] = 36.0, ["tireless.amount"] = 85.0, ["nimble.amount"] = 85.0, ["springstep.amount"] = 85.0, ["riverstride.amount"] = 85.0, ["quiet_steps.amount"] = 85.0, ["ward_fire.amount"] = 60.0, ["ward_frost.amount"] = 60.0, ["ward_lightning.amount"] = 48.0, ["ward_poison.amount"] = 48.0, ["ward_spirit.amount"] = 48.0, ["ward_blunt.amount"] = 48.0, ["ward_slash.amount"] = 48.0, ["ward_pierce.amount"] = 48.0, ["soft_landing.amount"] = 85.0, ["efficient_guard.amount"] = 85.0, ["firm_grip.amount"] = 80.0, ["stormspark.amount"] = 300.0, ["executioner.amount"] = 195.0, ["runic_reserve.amount"] = 100.0, ["veinbreaker.amount"] = 660.0, ["natures_favor.amount"] = 195.0, ["emergency_ward.amount"] = 105.0, ["windstep.amount"] = 65.0, ["runic_guard.amount"] = 90.0, ["second_breath.amount"] = 100.0, ["calm_heart.amount"] = 210.0, ["patient_recovery.amount"] = 210.0, ["deep_breath.amount"] = 210.0, ["steady_foothold.amount"] = 75.0, ["travelers_vigor.amount"] = 180.0, ["thors_judgment.amount"] = 1950.0, ["fenrirs_hunger.amount"] = 54.0, ["fatebreaker.amount"] = 1650.0, ["fatebreaker.boss"] = 600.0, ["phantom_strike.amount"] = 1500.0, ["phantom_strike.chance"] = 99.0, ["winter_sovereign.amount"] = 1800.0, ["runic_echo.amount"] = 1350.0, ["ymirs_awakening.amount"] = 2700.0, ["earths_bounty.amount"] = 250.0, ["eternal_temper.amount"] = 99.0, ["tireless_creator.amount"] = 100.0, ["tireless_creator.chance"] = 99.0, ["runic_momentum.amount"] = 90.0, ["freyrs_garden.amount"] = 85.0, ["aegirs_favor.amount"] = 99.0, ["yggdrasils_shelter.amount"] = 270.0, ["bifrost_stride.amount"] = 65.0, ["bifrost_stride.reduction"] = 85.0, ["einherjars_return.amount"] = 100.0, ["heimdalls_bastion.amount"] = 270.0, ["guardians_wrath.amount"] = 1350.0, ["runic_rebound.amount"] = 1500.0, ["fates_renewal.amount"] = 100.0, ["ancient_resolve.amount"] = 75.0, ["travelers_mercy.amount"] = 3.0, ["norns_respite.amount"] = 1050.0, ["undying_tenacity.amount"] = 75.0, ["dulled.amount"] = 64.0, ["faded_runes.amount"] = 64.0, ["exhausting.amount"] = 70.0, ["sluggish.amount"] = 30.0, ["brittle.amount"] = 80.0, ["heavy.amount"] = 200.0, ["restrictive.amount"] = 16.0, ["poor_guard.amount"] = 70.0, ["thin_plating.amount"] = 50.0, ["draining.amount"] = 30.0, ["frail.amount"] = 30.0, ["tiring.amount"] = 50.0, ["unbalanced.amount"] = 90.0, ["short_reach.amount"] = 60.0, ["slow_mending.amount"] = 50.0, ["burdened.amount"] = 50.0, ["vulnerable.amount"] = 36.0, ["runic_leak.amount"] = 70.0, ["work_stride.amount"] = 54.0, ["work_breath.amount"] = 180.0, ["work_reserve.amount"] = 90.0, ["pack_frame.amount"] = 75.0, ["work_recovery.amount"] = 85.0, ["tool_grace.amount"] = 85.0, ["tool_care.amount"] = 80.0, ["supply_sense.amount"] = 600.0, ["measured_work.amount"] = 85.0, ["rhythmic_work.amount"] = 100.0, ["craftsman_trance.amount"] = 85.0, ["field_second_wind.amount"] = 100.0, ["steady_supplies.amount"] = 360.0, ["repairing_touch.amount"] = 54.0, ["rough_care.amount"] = 130.0, ["fumbling.amount"] = 120.0, ["short_breath.amount"] = 50.0, ["overpacked.amount"] = 50.0, ["muffled_steps.amount"] = 80.0, ["shadowweave.amount"] = 75.0, ["featherbound.amount"] = 150.0, ["slow_feast.amount"] = 100.0, ["rain_shedding.amount"] = 85.0, ["camp_memory.amount"] = 100.0, ["measured_parry.amount"] = 80.0, ["longstride.amount"] = 85.0, ["rainwalker.amount"] = 65.0, ["anchor_stance.amount"] = 85.0, ["breath_of_hearth.amount"] = 480.0, ["guard_focus.amount"] = 480.0, ["skyward_leap.amount"] = 150.0, ["oathkeepers_reserve.amount"] = 240.0, ["storm_step.amount"] = 900.0, ["valkyrie_poise.amount"] = 85.0, ["opening_cut.amount"] = 195.0, ["desperate_edge.amount"] = 210.0, ["farshot.amount"] = 225.0, ["point_blank.amount"] = 150.0, ["ambushers_edge.amount"] = 180.0, ["retaliation_edge.amount"] = 360.0, ["guardbreaker.amount"] = 300.0, ["spellbattery.amount"] = 300.0, ["followthrough.amount"] = 360.0, ["tyrs_measure.amount"] = 1500.0, ["bloodstar.amount"] = 750.0, ["runesurge.amount"] = 100.0, ["quiet_construction.amount"] = 80.0, ["careful_demolition.amount"] = 85.0, ["leveling_focus.amount"] = 85.0, ["raising_focus.amount"] = 85.0, ["seed_focus.amount"] = 85.0, ["cultivation_focus.amount"] = 85.0, ["luring_glimmer.amount"] = 150.0, ["hook_window.amount"] = 200.0, ["bait_keeper.amount"] = 99.0, ["restoration_reserve.amount"] = 100.0, ["fortress_oath.amount"] = 75.0, ["weatherseal.amount"] = 95.0, ["artisan_renewal.amount"] = 100.0, ["verdant_clock.amount"] = 85.0, ["deep_roots.amount"] = 35.0, ["harvest_moon.amount"] = 80.0, ["unbroken_line.amount"] = 250.0, ["stillwater_pact.amount"] = 85.0, ["tidal_memory.amount"] = 100.0, ["hooked_fortune.amount"] = 99.0, ["earthshaper.amount"] = 180.0, ["stoneweaver.amount"] = 250.0, ["pathfinder.amount"] = 250.0, ["terraced_wisdom.amount"] = 180.0 }; } } public static class CompactPerkDescriptions { private static readonly IReadOnlyDictionary<string, string> Templates = CreateTemplates(); public static string Template(PerkDefinition def) { if (def == null) { throw new ArgumentNullException("def"); } if (def.DefinitionVersion != 1 || !Templates.TryGetValue(def.Id, out string value)) { return def.Trigger; } return PercentageText.Normalize(value); } private static IReadOnlyDictionary<string, string> CreateTemplates() { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal) { ["razor_edge"] = "+{amount}% physical damage", ["runic_potency"] = "+{amount}% existing elemental damage", ["efficient_strikes"] = "-{amount}% attack stamina cost", ["quickstrike"] = "+{amount}% melee attack speed", ["quick_draw"] = "+{amount}% bow draw speed", ["rapid_reload"] = "-{amount}% crossbow reload time", ["steady_aim"] = "-{amount}% aiming stamina cost", ["eitr_efficiency"] = "-{amount}% Eitr cost", ["punishing_blows"] = "+{amount}% direct damage vs staggered enemies", ["crushing_force"] = "+{amount}% direct-hit stagger", ["duelists_guard"] = "+{amount}% block power", ["reinforced"] = "+{amount}% max durability", ["lightweight"] = "-{amount}% item weight", ["frostbound"] = "+{amount}% base damage as Frost", ["emberbound"] = "+{amount}% base damage as Fire", ["resource_breaker"] = "+{amount}% gathering damage", ["efficient_labor"] = "-{amount}% work stamina cost", ["swift_labor"] = "+{amount}% work, build and reel speed", ["resource_magnet"] = "+{amount}% wood/stone/ore pickup radius while held", ["extended_reach"] = "+{amount}% tool reach", ["haulers_strength"] = "+{amount}% base carry capacity", ["builders_reach"] = "+{amount}% build/repair reach", ["green_hands"] = "-{amount}% planting stamina cost", ["patient_angler"] = "-{amount}% reeling stamina cost", ["long_cast"] = "+{amount}% cast distance and line length", ["fortified"] = "+{amount}% Armor", ["vitality"] = "+{amount}% max HP from base + food", ["endurance"] = "+{amount}% max stamina from base + food", ["runic_reservoir"] = "+{amount}% existing food Eitr", ["recovery"] = "+{amount}% HP regen", ["second_wind"] = "+{amount}% stamina regen", ["runic_flow"] = "+{amount}% Eitr regen", ["fleetfoot"] = "+{amount}% walk/run speed", ["tireless"] = "-{amount}% sprint stamina cost", ["nimble"] = "-{amount}% dodge stamina cost", ["springstep"] = "-{amount}% jump stamina cost", ["riverstride"] = "-{amount}% swim stamina cost", ["quiet_steps"] = "-{amount}% sneak stamina cost", ["soft_landing"] = "-{amount}% fall damage", ["efficient_guard"] = "-{amount}% block stamina cost", ["firm_grip"] = "-{amount}% knockback taken while blocking", ["stormspark"] = "Direct hit: chain {amount}% damage to 1 enemy within 8m; CD {cooldown}s", ["executioner"] = "+{amount}% direct damage vs enemies below 20% HP", ["runic_reserve"] = "Every 5th manual spell: refund {amount}% Eitr spent", ["veinbreaker"] = "Every 6th gather hit: +{amount}% gathering damage", ["natures_favor"] = "+{amount}% average natural-resource yield on first drops", ["master_repair"] = "Repair up to 3 owned pieces within {amount}m; CD {cooldown}s", ["emergency_ward"] = "Hit leaves HP below 30%: {amount}% max-HP barrier for 4s; CD {cooldown}s", ["windstep"] = "Dodge: +{amount}% movement for 2s; CD {cooldown}s", ["runic_guard"] = "Block: {amount}% max-HP barrier for 4s; CD {cooldown}s", ["second_breath"] = "Below 20% stamina: restore {amount}% max stamina; CD {cooldown}s", ["calm_heart"] = "8s unharmed: +{amount}% HP regen", ["patient_recovery"] = "Below 50% HP: +{amount}% HP regen", ["deep_breath"] = "Below 40% stamina: +{amount}% stamina regen", ["steady_foothold"] = "Taking damage: -{amount}% stagger taken for 4s; CD {cooldown}s", ["travelers_vigor"] = "Above 80% HP: +{amount}% stamina regen", ["thors_judgment"] = "Every 6th direct hit: {amount}% lightning damage, up to 3 enemies/8m; CD {cooldown}s", ["fenrirs_hunger"] = "Heal {amount}% of direct damage dealt; shared cap 3% max HP/s", ["fatebreaker"] = "+{amount}% direct damage vs enemies below 30% HP (bosses +{boss}%); CD {cooldown}s", ["phantom_strike"] = "Direct hit: {chance}% chance of {amount}% extra damage; CD {cooldown}s", ["winter_sovereign"] = "Every 8th direct hit: {amount}% frost damage in 5m; CD {cooldown}s", ["runic_echo"] = "Every 4th manual offensive cast: repeat {amount}% direct damage; CD {cooldown}s", ["ymirs_awakening"] = "Every 8th gather hit: {amount}% gathering damage in 3m; CD {cooldown}s", ["earths_bounty"] = "+{amount}% average natural-resource yield on first drops", ["eternal_temper"] = "{amount}% chance to avoid durability loss", ["tireless_creator"] = "Successful work: {chance}% chance to refund {amount}% stamina spent", ["runic_momentum"] = "Every 5th successful work action: +{amount}% work speed for 6s; CD {cooldown}s", ["worldsmith"] = "Repair up to 10 owned pieces within {amount}m; CD {cooldown}s", ["freyrs_garden"] = "Left Shift: plant 3x3, -{amount}% total stamina cost; needs all seeds", ["aegirs_favor"] = "Catch: {amount}% chance of 1 extra matching fish", ["yggdrasils_shelter"] = "Hit leaves HP below 35%: {amount}% max-HP barrier for 6s; CD {cooldown}s", ["bifrost_stride"] = "Dodge: +{amount}% movement for 3s, next dodge cost -{reduction}%; CD {cooldown}s", ["einherjars_return"] = "Survive lethal hit at {amount}% max HP + 2s protection; shared CD {cooldown}s", ["heimdalls_bastion"] = "3 blocks in 5s: {amount}% max-HP barrier for 6s; CD {cooldown}s", ["guardians_wrath"] = "Block: next direct melee hit within 3s deals +{amount}%; CD {cooldown}s", ["runic_rebound"] = "Block enemy projectile: reflect {amount}% incoming damage; CD {cooldown}s", ["fates_renewal"] = "Below 15% stamina: restore {amount}% max stamina; CD {cooldown}s", ["ancient_resolve"] = "Taking damage: -{amount}% damage taken for 4s; CD {cooldown}s", ["travelers_mercy"] = "Dodge: heal {amount}% max HP (shared cap 3%/s); CD {cooldown}s", ["norns_respite"] = "12s unharmed: +{amount}% HP regen", ["undying_tenacity"] = "Below 30% HP: -{amount}% stagger taken", ["dulled"] = "-{amount}% physical/gathering damage", ["faded_runes"] = "-{amount}% existing elemental spell damage", ["exhausting"] = "+{amount}% stamina costs", ["sluggish"] = "-{amount}% attack/work animation speed", ["brittle"] = "-{amount}% max durability", ["heavy"] = "+{amount}% item weight", ["restrictive"] = "-{amount}% movement speed", ["poor_guard"] = "-{amount}% block power", ["thin_plating"] = "-{amount}% Armor", ["draining"] = "-{amount}% max stamina from base + food", ["frail"] = "-{amount}% max HP from base + food", ["tiring"] = "-{amount}% stamina regen", ["unbalanced"] = "+{amount}% knockback taken", ["short_reach"] = "-{amount}% tool reach", ["slow_mending"] = "-{amount}% HP regen", ["burdened"] = "-{amount}% base carry capacity", ["vulnerable"] = "+{amount}% physical damage taken after resistance", ["runic_leak"] = "+{amount}% Eitr costs", ["work_stride"] = "Successful work: +{amount}% movement for 8s with same tool", ["work_breath"] = "Successful work: +{amount}% stamina regen for 8s with same tool", ["work_reserve"] = "+{amount}% max stamina from base + food while held", ["pack_frame"] = "-{amount}% materials/seed/bait/fish weight while held", ["work_recovery"] = "Successful work: -{amount}% remaining stamina regen delay", ["tool_grace"] = "-{amount}% tool equip/sheathe time", ["tool_care"] = "-{amount}% tool durability loss", ["supply_sense"] = "+{amount}% materials/seed/bait/fish pickup radius while held", ["measured_work"] = "After 3 work successes: -{amount}% work cost; resets on swap/10s idle", ["rhythmic_work"] = "Every 3rd work success: refund {amount}% stamina spent", ["craftsman_trance"] = "Every 6th work success: next 3 work costs -{amount}%; CD {cooldown}s; reset on swap/10s idle", ["field_second_wind"] = "Every 8th work success: restore {amount}% max stamina; CD {cooldown}s", ["steady_supplies"] = "Every 8th work success: +{amount}% carry capacity for 12s with same tool; CD {cooldown}s", ["repairing_touch"] = "Every 10th work success: repair {amount}% missing tool durability; CD {cooldown}s", ["tool_courier"] = "Every 8th work success: pull up to 24 supplies within {amount}m; CD {cooldown}s", ["rough_care"] = "+{amount}% tool durability loss", ["fumbling"] = "+{amount}% tool equip/sheathe time", ["short_breath"] = "Successful work: -{amount}% stamina regen for 8s with same tool", ["overpacked"] = "+{amount}% materials/seed/bait/fish weight while held", ["muffled_steps"] = "-{amount}% noise radius (80% cap)", ["shadowweave"] = "-{amount}% visibility while sneaking (75% cap)", ["featherbound"] = "+{amount}% jump height (150% cap)", ["slow_feast"] = "+{amount}% food duration (100% cap)", ["rain_shedding"] = "-{amount}% Wet duration (85% cap)", ["camp_memory"] = "+{amount}% next Rested duration (100% cap)", ["measured_parry"] = "+{amount}% shield parry bonus (80% cap)", ["longstride"] = "Running for 4s: -{amount}% jump stamina cost", ["rainwalker"] = "While Wet: +{amount}% movement speed", ["anchor_stance"] = "After blocking still for 1.5s: -{amount}% block cost", ["breath_of_hearth"] = "While Resting: +{amount}% HP regen", ["guard_focus"] = "Block: +{amount}% stamina regen for 6s; CD {cooldown}s", ["skyward_leap"] = "1 extra jump/landing, +{amount}% height (150% cap); stamina required; CD {cooldown}s", ["oathkeepers_reserve"] = "Block: gain {amount}% blocked damage as stamina (15% max-stamina cap); CD {cooldown}s", ["storm_step"] = "Dodge: +{amount}% stamina regen for 4s; CD {cooldown}s", ["valkyrie_poise"] = "-{amount}% fall damage; 30% prevented damage to stamina (20% max cap); refund CD {cooldown}s", ["opening_cut"] = "+{amount}% direct damage vs targets above 90% HP", ["desperate_edge"] = "Below 25% stamina: +{amount}% direct damage", ["farshot"] = "+{amount}% direct projectile damage at 15m+ from launch", ["point_blank"] = "+{amount}% direct projectile damage within 5m of launch", ["ambushers_edge"] = "+{amount}% direct damage vs unalerted enemies", ["retaliation_edge"] = "After taking HP damage: +{amount}% direct damage for 4s", ["guardbreaker"] = "+{amount}% direct damage vs blocking humanoids", ["spellbattery"] = "Above 80% Eitr: +{amount}% direct spell damage", ["followthrough"] = "Direct hit: next direct hit on a different enemy within 3s deals +{amount}%", ["tyrs_measure"] = "Below 50% HP: +{amount}% direct boss damage; CD {cooldown}s", ["bloodstar"] = "Direct enemy kill: +{amount}% direct damage for 6s; no buffed-kill refresh; CD {cooldown}s", ["runesurge"] = "Direct spell hit: refund {amount}% Eitr spent (20% max Eitr/cast cap); CD {cooldown}s", ["quiet_construction"] = "-{amount}% build/repair/demolition noise", ["careful_demolition"] = "-{amount}% successful demolition stamina cost", ["leveling_focus"] = "-{amount}% level-ground stamina cost", ["raising_focus"] = "-{amount}% raise-ground stamina cost", ["seed_focus"] = "-{amount}% planting stamina cost", ["cultivation_focus"] = "-{amount}% cultivation stamina cost", ["luring_glimmer"] = "+{amount}% fish attraction radius; correct bait required", ["hook_window"] = "+{amount}% manual hook window (base 0.5s); correct bait", ["bait_keeper"] = "Catch: {amount}% chance to return 1 bait used", ["restoration_reserve"] = "Manual repair: restore {amount}% max stamina; CD {cooldown}s", ["fortress_oath"] = "New owned pieces take {amount}% less hit damage (75% cap)", ["weatherseal"] = "New owned pieces take {amount}% less weather damage; need support", ["artisan_renewal"] = "Manual repair: restore {amount}% missing hammer durability; CD {cooldown}s", ["verdant_clock"] = "New plants: -{amount}% growth time (85% cap); normal growing conditions", ["deep_roots"] = "New plants: -{amount}% spacing radius (35% cap); normal growing conditions", ["harvest_moon"] = "Plant: advance up to 6 nearby owned healthy plants by {amount}% base growth; CD {cooldown}s", ["unbroken_line"] = "+{amount}% line stretch before breaking", ["stillwater_pact"] = "Hooked fish struggling: -{amount}% fishing stamina cost", ["tidal_memory"] = "Catch: restore {amount}% max stamina; CD {cooldown}s", ["hooked_fortune"] = "Correct-bait nibble: {amount}% auto-hook chance; reel normally; CD {cooldown}s", ["earthshaper"] = "+{amount}% level-ground radius (8m cap); whole area needs permission", ["stoneweaver"] = "+{amount}% height per paid raise-ground action; native terrain limits", ["pathfinder"] = "+{amount}% paint-ground radius (8m cap); whole area needs permission", ["terraced_wisdom"] = "+{amount}% smoothing radius (8m cap); whole area needs permission", ["flanking_edge"] = "+{amount}% direct melee damage from rear 120-degree arc", ["dueling_edge"] = "+{amount}% direct melee damage vs sole enemy within 6m", ["surrounded_edge"] = "3+ enemies within 8m: +{amount}% direct melee damage", ["relentless_edge"] = "+{amount}% direct melee damage vs last directly hit enemy for 3s; no stacking", ["grounded_impact"] = "+{amount}% melee knockback vs grounded enemies", ["martial_rhythm"] = "Every 3rd paid melee attack with direct damage: refund {amount}% stamina spent (85% cap)", ["highground"] = "+{amount}% direct projectile damage launched 3m+ above impact", ["giant_hunter"] = "+{amount}% direct projectile damage vs enemies with 3x your max HP", ["airborne_hunter"] = "+{amount}% direct projectile damage vs airborne enemies; not swimmers", ["steady_release"] = "At impact: above 50% stamina + 8s unharmed, +{amount}% direct projectile damage", ["pinning_shot"] = "+{amount}% projectile stagger at 15m+ from launch; no extra HP damage", ["shield_breath"] = "Enemy block: refund {amount}% block stamina spent (85% cap); CD {cooldown}s", ["shield_mending"] = "Enemy block: repair {amount}% missing shield durability; CD {cooldown}s", ["shield_rebuke"] = "Enemy melee block: return {amount}% blocked damage as Frost; CD {cooldown}s", ["shield_composure"] = "Enemy projectile block: -{amount}% stamina regen delay (85% cap); CD {cooldown}s", ["parry_riposte"] = "Parry: +{amount}% direct melee damage for 4s with same shield; CD {cooldown}s", ["wet_conduit"] = "+{amount}% direct spell damage vs Wet enemies", ["cinder_hunter"] = "+{amount}% direct spell damage vs Burning enemies", ["frozen_focus"] = "+{amount}% direct spell damage vs Frost-slowed enemies", ["arcane_precision"] = "At impact: above 75% HP + 50% Eitr, +{amount}% direct spell damage", ["spell_repair"] = "Direct spell hit: repair {amount}% missing staff durability; CD {cooldown}s", ["sorcerers_breath"] = "Direct spell kill: restore {amount}% max stamina; no DOT/procs; CD {cooldown}s", ["soul_shelter"] = "Direct spell kill: {amount}% max-HP barrier for 6s, strongest only; no DOT/procs; CD {cooldown}s" }; string[] array = new string[8] { "fire", "frost", "lightning", "poison", "spirit", "blunt", "slash", "pierce" }; foreach (string text in array) { dictionary.Add("ward_" + text, "-{amount}% incoming " + text + " damage after resistance"); } foreach (ExpansionPerk value in Expansion045.Details.Values) { dictionary.Add(value.Definition.Id, value.Compact); } return dictionary; } } public static class ConfigurationValidator { public static List<string> Validate(GameplayConfig config, IEnumerable<ItemProfile>? profiles = null, bool requireDiversePools = true) { List<string> list = new List<string>(); if (config.SchemaVersion != 1) { list.Add("Unsupported gameplay configuration schema."); } if (config.PerkPlacementVersion < 0 || config.PerkPlacementVersion > 1) { list.Add("Unsupported perk placement policy version."); } if (config.Rarities == null || config.StrengthBands == null || config.Perks == null || config.Limits == null || config.ItemOverrides == null || config.Forge == null) { list.Add("Configuration tables must not be null."); return list; } if (config.Rarities.Any((RarityDefinition r) => r == null) || config.StrengthBands.Any((StrengthBand b) => b == null) || config.Perks.Any((PerkDefinition p) => p == null) || config.ItemOverrides.Any((ItemOverride o) => o == null)) { list.Add("Configuration tables must not contain null entries."); return list; } bool flag = config.Rarities.Any((RarityDefinition r) => r.Rank == 0); if (config.Forge.SalvageReturnPercent < 0 || config.Forge.SalvageReturnPercent > 50) { list.Add("Forge salvage return percentage must be between 0 and 50."); } if (config.Rarities.Count != (flag ? 19 : 18)) { list.Add("The full 18 nonzero rarities are required, optionally with rank-zero Default as a nineteenth outcome."); } if (((IEnumerable<RarityDefinition>)config.Rarities).Sum((Func<RarityDefinition, long>)((RarityDefinition r) => r.Weight)) != 10000) { list.Add("Rarity weights must total exactly 10,000; automatic normalization is forbidden."); } if (config.Rarities.Select((RarityDefinition r) => r.Id).Distinct<string>(StringComparer.Ordinal).Count() != config.Rarities.Count) { list.Add("Rarity IDs must be unique."); } if (config.Rarities.Select((RarityDefinition r) => r.Rank).Distinct().Count() != config.Rarities.Count) { list.Add("Rarity ranks must be unique."); } foreach (RarityDefinition rarity in config.Rarities) { if (string.IsNullOrWhiteSpace(rarity.Id) || string.IsNullOrWhiteSpace(rarity.Name)) { list.Add("Rarity IDs and names must be present."); } if (rarity.Weight < 0 || rarity.Rank < -6 || rarity.Rank > 12) { list.Add("Invalid rarity weight or rank: " + rarity.Id); } if (rarity.Rank == 0 != (rarity.Id == "default")) { list.Add("Only Default may use rank zero: " + rarity.Id); } if (PerkSelector.Slots(rarity).Any((int n) => n < 0 || n > 12) || ((IEnumerable<int>)PerkSelector.Slots(rarity)).Sum((Func<int, long>)((int n) => n)) != Math.Abs((long)rarity.Rank)) { list.Add("Perk allocation must equal absolute rarity rank: " + rarity.Id); } if ((rarity.Rank < 0 && rarity.NegativeSlots != -rarity.Rank) || (rarity.Rank > 0 && rarity.NegativeSlots != 0)) { list.Add("Only negative rarities allocate penalties: " + rarity.Id); } if (rarity.UltraSlots > 0 && rarity.Rank < 9) { list.Add("Ultra perks require rarity rank +9 or higher."); } if (!Regex.IsMatch(rarity.Color ?? "", "^#[0-9a-fA-F]{6}$")) { list.Add("Invalid rarity color: " + rarity.Id); } } if (config.StrengthBands.Count == 0 || ((IEnumerable<StrengthBand>)config.StrengthBands).Sum((Func<StrengthBand, long>)((StrengthBand b) => b.Weight)) != 10000) { list.Add("Strength band weights must total exactly 10,000."); } int num = 0; foreach (StrengthBand strengthBand in config.StrengthBands) { if (strengthBand.MinScore != num || strengthBand.MaxScore < strengthBand.MinScore || strengthBand.MaxScore > 10000 || strengthBand.Weight < 0) { list.Add("Strength bands must cover scores 0–10000 exactly once in order, without gaps or overlap."); } num = strengthBand.MaxScore + 1; } if (num != 10001) { list.Add("Strength bands must include the exact score 10000."); } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (PerkDefinition perk in config.Perks) { if (!hashSet.Add(perk.Id) || string.IsNullOrWhiteSpace(perk.Id)) { list.Add("Missing or duplicate perk ID: " + perk.Id); } if (string.IsNullOrWhiteSpace(perk.Name) || string.IsNullOrWhiteSpace(perk.Effect) || string.IsNullOrWhiteSpace(perk.Trigger) || string.IsNullOrWhiteSpace(perk.Stacking) || string.IsNullOrWhiteSpace(perk.SafetyLimits)) { list.Add("Perk must declare its name, implemented effect, trigger, stacking, and safety rules: " + perk.Id); } if (perk.Weight < 0 || perk.DefinitionVersion < 1 || !Enum.IsDefined(typeof(PerkClass), perk.Class)) { list.Add("Invalid perk weight, class, or definition version: " + perk.Id); } if (perk.Categories == null || perk.RequiredCapabilities == null || perk.ExcludedNativeEffects == null || perk.ExclusionGroups == null || perk.IncompatibleIds == null || perk.Parameters == null) { list.Add("Perk metadata collections must be defined: " + perk.Id); continue; } if (perk.Parameters.Count == 0) { list.Add("Every perk requires a randomized numerical parameter: " + perk.Id); } HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); foreach (PerkParameter parameter in perk.Parameters) { if (parameter == null) { list.Add("Perk parameter must not be null: " + perk.Id); continue; } if (!hashSet2.Add(parameter.Id) || string.IsNullOrWhiteSpace(parameter.Id)) { list.Add("Duplicate or absent parameter: " + perk.Id); } if (!Finite(parameter.Min) || !Finite(parameter.Max) || parameter.Min >= parameter.Max || parameter.Precision < 0 || parameter.Precision > 6 || string.IsNullOrWhiteSpace(parameter.Unit)) { list.Add("Invalid randomized parameter range, precision, or unit: " + perk.Id + "." + parameter.Id); } if (parameter.HighRankMaxima == null) { list.Add("High-rank maxima must be an object, empty for untiered or historical ranges: " + perk.Id + "." + parameter.Id); } else { if (parameter.HighRankMaxima.Count <= 0) { continue; } if (parameter.HighRankMaxima.Count != 4) { list.Add("A high-rank maximum map must declare all four ranks 9 through 12, or be empty: " + perk.Id + "." + parameter.Id); } if (!Balance021.AllowsHighRankMaximum(perk.Id, parameter.Id) || perk.Class == PerkClass.Negative || parameter.Unit != "%" || !parameter.HigherIsBetter) { list.Add("High-rank maxima are only supported for reviewed uncapped beneficial percentages: " + perk.Id + "." + parameter.Id); } double num2 = parameter.Max; for (int num3 = 9; num3 <= 12; num3++) { if (parameter.HighRankMaxima.TryGetValue(num3, out var value)) { if (!Finite(value) || value < num2 || value > 9000.0) { list.Add("High-rank maxima must be finite, nondecreasing, at least the base maximum and at most 9000: " + perk.Id + "." + parameter.Id); } num2 = value; } } if (parameter.HighRankMaxima.Keys.Any((int rank) => rank < 9 || rank > 12)) { list.Add("High-rank maxima support only ranks 9 through 12: " + perk.Id + "." + parameter.Id); } } } } if (((IEnumerable<PerkDefinition>)config.Perks).Sum((Func<PerkDefinition, long>)((PerkDefinition p) => p.Weight)) > int.MaxValue) { list.Add("Perk weight total exceeds supported integer range."); } foreach (PerkDefinition item in config.Perks.Where((PerkDefinition p) => p.IncompatibleIds != null)) { foreach (string incompatibleId in item.IncompatibleIds) { if (!hashSet.Contains(incompatibleId) || incompatibleId == item.Id) { list.Add("Unknown or self-referencing incompatible perk: " + item.Id + " -> " + incompatibleId); } } } BalanceLimits limits = config.Limits; double[] array = new double[6] { limits.MovementSpeedPercent, limits.AttackSpeedPercent, limits.CostReductionPercent, limits.DamageReductionPercent, limits.ResourceBonusPercent, limits.HealingMaxHealthPerSecondPercent }; foreach (double num5 in array) { if (!Finite(num5) || num5 < 0.0 || num5 > 1000.0) { list.Add("Aggregate percent caps must be finite and between 0 and 1000."); } } if (limits.CostReductionPercent >= 100.0 || limits.DamageReductionPercent >= 100.0) { list.Add("Cost and damage reduction caps must be below 100%."); } if (!Finite(limits.StatFloorMultiplier) || limits.StatFloorMultiplier <= 0.0 || limits.StatFloorMultiplier > 1.0 || !Finite(limits.MaximumCostMultiplier) || limits.MaximumCostMultiplier < 1.0 || limits.MaximumCostMultiplier > 20.0) { list.Add("Stat floors and action-cost limits must preserve usable equipment."); } HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal); foreach (ItemOverride itemOverride in config.ItemOverrides) { if (string.IsNullOrWhiteSpace(itemOverride.Prefab) || !hashSet3.Add(itemOverride.Prefab) || (itemOverride.Enabled && string.IsNullOrWhiteSpace(itemOverride.Category)) || itemOverride.Capabilities == null || itemOverride.NativeEffects == null) { list.Add("Invalid or duplicate supported-item override: " + itemOverride.Prefab); } } if (list.Count == 0 && profiles != null) { foreach (ItemProfile profile in profiles) { list.AddRange(ValidateProfile(config, profile, requireDiversePools)); } } return list; } public static List<string> ValidateProfile(GameplayConfig config, ItemProfile profile, bool requireDiversePools = false) { List<string> list = new List<string>(); if (string.IsNullOrWhiteSpace(profile.Category) || profile.Capabilities == null || profile.NativeEffects == null) { list.Add("Item classification is incomplete: " + profile.Prefab); return list; } PerkSelector perkSelector = new PerkSelector(config.Perks, config.PerkPlacementVersion); foreach (RarityDefinition rarity in config.Rarities) { try { if (!perkSelector.CanComplete(profile, rarity)) { list.Add(profile.Prefab + " (" + profile.Category + ") cannot complete " + rarity.Name + "."); } } catch (InvalidOperationException ex) { list.Add(profile.Prefab + ": " + ex.Message); break; } } if (requireDiversePools) { int[] array = ((config.PerkPlacementVersion != 0) ? new int[4] { 10, 3, 4, 6 } : new int[4] { 15, 6, 6, 8 }); int cls; for (cls = 0; cls < 4; cls++) { if (config.Rarities.Any((RarityDefinition r) => PerkSelector.Slots(r)[cls] > 0)) { int num = config.Perks.Count((PerkDefinition p) => p.Class == (PerkClass)cls && Compatibility.Supports(p, profile, config.PerkPlacementVersion)); if (num < array[cls]) { string[] obj = new string[8] { profile.Category, " has ", num.ToString(), " eligible ", null, null, null, null }; PerkClass perkClass = (PerkClass)cls; obj[4] = perkClass.ToString(); obj[5] = " perks; requires at least "; obj[6] = array[cls].ToString(); obj[7] = " for random variety."; list.Add(string.Concat(obj)); } } } } return list; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } public sealed class DamageContribution { public string OwnerId { get; set; } = ""; public string OriginId { get; set; } = ""; public string SourceId { get; set; } = "base"; public string Element { get; set; } = "damage"; public double Amount { get; set; } public DamageContribution Copy(double? amount = null) { return new DamageContribution { OwnerId = OwnerId, OriginId = OriginId, SourceId = SourceId, Element = Element, Amount = (amount ?? Amount) }; } } public static class DamagePartition { public static bool Finite(double v) { if (!double.IsNaN(v)) { return !double.IsInfinity(v); } return false; } public static List<DamageContribution> Reconcile(IEnumerable<DamageContribution> sources, IReadOnlyDictionary<string, double> typedDamage, double actualDamage, string fallbackOwner = "", string fallbackOrigin = "") { if (!Finite(actualDamage) || actualDamage <= 0.0) { return new List<DamageContribution>(); } List<DamageContribution> source = sources.Where((DamageContribution x) => Finite(x.Amount) && x.Amount > 0.0).ToList(); List<DamageContribution> list = new List<DamageContribution>(); foreach (KeyValuePair<string, double> pair in typedDamage.Where<KeyValuePair<string, double>>((KeyValuePair<string, double> x) => Finite(x.Value) && x.Value > 0.0)) { List<DamageContribution> source2 = source.Where((DamageContribution x) => x.Element == pair.Key).ToList(); double total = source2.Sum((DamageContribution x) => x.Amount); if (total <= 0.0) { list.Add(new DamageContribution { OwnerId = fallbackOwner, OriginId = fallbackOrigin, Element = pair.Key, Amount = pair.Value }); } else { list.AddRange(source2.Select((DamageContribution x) => x.Copy(pair.Value * x.Amount / total))); } } double num = list.Sum((DamageContribution x) => x.Amount); if (num <= 0.0) { return new List<DamageContribution> { new DamageContribution { OwnerId = fallbackOwner, OriginId = fallbackOrigin, Amount = actualDamage } }; } foreach (DamageContribution item in list) { item.Amount *= actualDamage / num; } list.OrderByDescending((DamageContribution x) => x.Amount).First().Amount += actualDamage - list.Sum((DamageContribution x) => x.Amount); return Merge(list); } public static List<DamageContribution> Merge(IEnumerable<DamageContribution> values) { return (from x in values where Finite(x.Amount) && x.Amount > 0.0 group x by x.OwnerId + "\u001f" + x.SourceId + "\u001f" + x.Element).Select(delegate(IGrouping<string, DamageContribution> g) { DamageContribution damageContribution = g.Last().Copy(); damageContribution.Amount = g.Sum((DamageContribution x) => x.Amount); return damageContribution; }).ToList(); } public static List<DamageContribution> RoundPartition(IEnumerable<DamageContribution> values, double total) { List<DamageContribution> list = Merge(values); if (list.Count == 0) { return list; } long num = (long)Math.Round(Math.Max(0.0, total) * 1000.0, MidpointRounding.AwayFromZero); double num2 = list.Sum((DamageContribution x) => x.Amount); if (num2 <= 0.0) { return new List<DamageContribution>(); } List<(int, double)> list2 = new List<(int, double)>(); long num3 = 0L; for (int num4 = 0; num4 < list.Count; num4++) { double num5 = list[num4].Amount / num2 * (double)num; long num6 = (long)Math.Floor(num5); list[num4].Amount = (double)num6 / 1000.0; num3 += num6; list2.Add((num4, num5 - (double)num6)); } foreach (var item in (from x in list2 orderby x.fraction descending, x.index select x).Take((int)Math.Max(0L, num - num3))) { list[item.Item1].Amount += 0.001; } return list; } } public sealed class DamageLedgerSnapshot { public double WindowSeconds { get; set; } public double TotalDamage { get; set; } public double Dps => TotalDamage / WindowSeconds; public int HitCount { get; set; } public bool CapacityLimited { get; set; } public List<DamageContribution> Sources { get; set; } = new List<DamageContribution>(); } public sealed class DamageLedger { private sealed class Entry { internal double Time; internal double Actual; internal string Id = ""; internal List<DamageContribution> Parts = new List<DamageContribution>(); } public const int MaximumEventSources = 256; private readonly LinkedList<Entry> entries = new LinkedList<Entry>(); private readonly Dictionary<string, double> ids = new Dictionary<string, double>(StringComparer.Ordinal); private readonly Queue<(string id, double time)> idOrder = new Queue<(string, double)>(); private readonly object gate = new object(); private readonly int capacity; private readonly double retention; private double lastEviction = double.NegativeInfinity; public int RetainedEvents { get { lock (gate) { return entries.Count; } } } public int RetainedIds { get { lock (gate) { return ids.Count; } } } public DamageLedger(int capacity = 60000, double retentionSeconds = 1800.0) { if (capacity < 1 || retentionSeconds < 1.0) { throw new ArgumentOutOfRangeException(); } this.capacity = capacity; retention = retentionSeconds; } public bool Add(string id, double now, double actual, IEnumerable<DamageContribution> contributions) { if (string.IsNullOrWhiteSpace(id) || !DamagePartition.Finite(now) || !DamagePartition.Finite(actual) || actual <= 0.0) { return false; } lock (gate) { Prune(now); if (ids.ContainsKey(id)) { return false; } List<DamageContribution> list = DamagePartition.Merge(contributions); double num = list.Sum((DamageContribution x) => x.Amount); if (list.Count == 0 || list.Count > 256 || !DamagePartition.Finite(num) || num <= 0.0) { return false; } foreach (DamageContribution item in list) { item.Amount *= actual / num; } ids[id] = now; idOrder.Enqueue((id, now)); entries.AddLast(new Entry { Id = id, Time = now, Actual = actual, Parts = list }); while (entries.Count > capacity) { lastEviction = entries.First.Value.Time; entries.RemoveFirst(); } while (ids.Count > capacity * 2) { (string, double) tuple = idOrder.Dequeue(); ids.Remove(tuple.Item1); } return true; } } private void Prune(double now) { while (entries.First != null && entries.First.Value.Time <= now - retention) { entries.RemoveFirst(); } while (idOrder.Count > 0 && idOrder.Peek().time <= now - retention) { (string, double) tuple = idOrder.Dequeue(); ids.Remove(tuple.Item1); } } public DamageLedgerSnapshot Snapshot(double now, double windowSeconds) { if (!DamagePartition.Finite(now) || !DamagePartition.Finite(windowSeconds) || windowSeconds <= 0.0) { throw new ArgumentOutOfRangeException(); } lock (gate) { Prune(now); windowSeconds = Math.Min(retention, windowSeconds); List<Entry> list = entries.Where((Entry x) => x.Time > now - windowSeconds && x.Time <= now).ToList(); return new DamageLedgerSnapshot { WindowSeconds = windowSeconds, TotalDamage = list.Sum((Entry x) => x.Actual), HitCount = list.Count, CapacityLimited = (lastEviction > now - windowSeconds), Sources = DamagePartition.Merge(list.SelectMany((Entry x) => x.Parts)) }; } } public void Clear() { lock (gate) { entries.Clear(); ids.Clear(); idOrder.Clear(); lastEviction = double.NegativeInfinity; } } } public sealed class DamageOverTimePool { private sealed class Bucket { internal List<DamageContribution> Remaining = new List<DamageContribution>(); internal List<DamageContribution> LastWeights = new List<DamageContribution>(); } public const int MaximumSources = 1024; private readonly Dictionary<string, Bucket> buckets = new Dictionary<string, Bucket>(StringComparer.Ordinal); private Bucket Get(string element) { if (!buckets.TryGetValue(element, out Bucket value)) { value = (buckets[element] = new Bucket()); } return value; } public void Add(string element, double nativeBefore, double nativeAfter, IEnumerable<DamageContribution> incoming) { Bucket bucket = Get(element); Sync(bucket, element, Math.Max(0.0, nativeBefore)); double num = Math.Max(0.0, nativeAfter - nativeBefore); if (num > 0.0) { bucket.Remaining = Bound(DamagePartition.Merge(bucket.Remaining.Concat(Scale(incoming, element, num))), element); } bucket.LastWeights = bucket.Remaining.Select((DamageContribution x) => x.Copy()).ToList(); } public void Replace(string element, double nativeBefore, double attempted, double nativeAfter, IEnumerable<DamageContribution> incoming) { Bucket bucket = Get(element); Sync(bucket, element, Math.Max(0.0, nativeBefore)); if (attempted >= nativeBefore) { bucket.Remaining = Bound(Scale(incoming, element, Math.Max(0.0, nativeAfter)), element); bucket.LastWeights = bucket.Remaining.Select((DamageContribution x) => x.Copy()).ToList(); } } public List<DamageContribution> Take(string element, double tickDamage) { if (tickDamage <= 0.0) { return new List<DamageContribution>(); } Bucket bucket = Get(element); List<DamageContribution> result = Scale((bucket.Remaining.Count > 0) ? bucket.Remaining : bucket.LastWeights, element, tickDamage); double num = bucket.Remaining.Sum((DamageContribution x) => x.Amount); if (num > 0.0) { bucket.Remaining = Scale(bucket.Remaining, element, Math.Max(0.0, num - tickDamage)); } return result; } private static List<DamageContribution> Scale(IEnumerable<DamageContribution> source, string element, double amount) { if (amount <= 0.0) { return new List<DamageContribution>(); } List<DamageContribution> list = DamagePartition.Merge(source.Where((DamageContribution x) => x.Element == element)); double num = list.Sum((DamageContribution x) => x.Amount); if (num <= 0.0) { return new List<DamageContribution> { new DamageContribution { Element = element, Amount = amount } }; } foreach (DamageContribution item in list) { item.Amount *= amount / num; } return list; } private static List<DamageContribution> Bound(List<DamageContribution> rows, string element) { if (rows.Count <= 1024) { return rows; } List<DamageContribution> list = rows.OrderByDescending((DamageContribution x) => x.Amount).Take(1023).ToList(); list.Add(new DamageContribution { Element = element, Amount = rows.Sum((DamageContribution x) => x.Amount) - list.Sum((DamageContribution x) => x.Amount) }); return list; } private static void Sync(Bucket b, string element, double native) { double num = b.Remaining.Sum((DamageContribution x) => x.Amount); if (num > native) { b.Remaining = Scale(b.Remaining, element, native); } else if (native > num) { b.Remaining.Add(new DamageContribution { Element = element, Amount = native - num }); } } } public static class Defaults { public const string Version = "0.5.4"; public static List<RarityDefinition> Rarities() { return new List<RarityDefinition> { R("worthless", "Worthless", -6, 30, "#60646C"), R("ruined", "Ruined", -5, 70, "#746357"), R("broken", "Broken", -4, 150, "#8D5550"), R("damaged", "Damaged", -3, 250, "#AA725C"), R("worn", "Worn", -2, 300, "#A8977C"), R("inferior", "Inferior", -1, 400, "#B6B0A3"), R("default", "Default", 0, 3500, "#B9B9B9"), R("common", "Common", 1, 2500, "#E0E0E0"), R("uncommon", "Uncommon", 2, 1500, "#63C76A"), R("rare", "Rare", 3, 660, "#4B92F1"), R("very_rare", "Very Rare", 4, 300, "#50CFD8"), R("exceptional", "Exceptional", 5, 150, "#6B63E8"), R("epic", "Epic", 6, 75, "#AB62EB", 5, 1), R("heroic", "Heroic", 7, 40, "#DF69B2", 6, 1), R("legendary", "Legendary", 8, 20, "#F49B3D", 6, 2), R("mythic", "Mythic", 9, 18, "#F05B68", 6, 2, 1), R("exalted", "Exalted", 10, 15, "#DDB52F", 6, 2, 2), R("divine", "Divine", 11, 12, "#FFE48A", 7, 2, 2), R("transcendent", "Transcendent", 12, 10, "#BEF6FF", 7, 2, 3) }; } private static RarityDefinition R(string id, string name, int rank, int weight, string color, int standard = 0, int special = 0, int ultra = 0) { return new RarityDefinition { Id = id, Name = name, Rank = rank, Weight = weight, Color = color, NegativeSlots = ((rank < 0) ? (-rank) : 0), StandardSlots = ((rank >= 0) ? ((standard == 0) ? rank : standard) : 0), SpecialSlots = special, UltraSlots = ultra }; } public static List<StrengthBand> StrengthBands() { return new List<StrengthBand> { new StrengthBand { MinScore = 0, MaxScore = 999, Weight = 8000 }, new StrengthBand { MinScore = 1000, MaxScore = 2499, Weight = 1400 }, new StrengthBand { MinScore = 2500, MaxScore = 4999, Weight = 450 }, new StrengthBand { MinScore = 5000, MaxScore = 7999, Weight = 120 }, new StrengthBand { MinScore = 8000, MaxScore = 9499, Weight = 28 }, new StrengthBand { MinScore = 9500, MaxScore = 10000, Weight = 2 } }; } } public static class EffectiveRange { public static PerkParameter ForRank(PerkParameter parameter, int rank) { if (parameter == null) { throw new ArgumentNullException("parameter"); } double value; double max = ((parameter.HighRankMaxima != null && parameter.HighRankMaxima.TryGetValue(rank, out value)) ? value : parameter.Max); return new PerkParameter { Id = parameter.Id, Min = parameter.Min, Max = max, Unit = parameter.Unit, Precision = parameter.Precision, HigherIsBetter = parameter.HigherIsBetter, HighRankMaxima = new Dictionary<int, double>() }; } } public static class Expansion045 { public const int AddedCount = 47; public const int CatalogueCount = 248; private static readonly Lazy<IReadOnlyDictionary<string, ExpansionPerk>> Metadata = new Lazy<IReadOnlyDictionary<string, ExpansionPerk>>(() => Create().ToDictionary<ExpansionPerk, string>((ExpansionPerk p) => p.Definition.Id, StringComparer.Ordinal)); public static IReadOnlyDictionary<string, ExpansionPerk> Details => Metadata.Value; public static List<ExpansionPerk> Create() { List<ExpansionPerk> list = MeleeExpansion045.Create(); list.AddRange(RangedExpansion045.Create()); list.AddRange(GuardExpansion045.Create()); if (list.Count != 47 || list.Select((ExpansionPerk p) => p.Definition.Id).Distinct<string>(StringComparer.Ordinal).Count() != 47) { throw new InvalidOperationException("The 0.4.5 expansion must contain exactly 47 distinct mechanics."); } return list; } public static void Add(List<PerkDefinition> definitions) { definitions.AddRange(from p in Create() select p.Definition); } } public sealed class ExpansionPerk { public PerkDefinition Definition { get; set; } = new PerkDefinition(); public string Compact { get; set; } = ""; public string When { get; set; } = ""; public string Example { get; set; } = ""; public string Notes { get; set; } = ""; public string GuideMode { get; set; } = ""; public string GuideCategory { get; set; } = "combat"; public string ScoreTarget { get; set; } = ""; public string ScoreMode { get; set; } = ""; public string ScoreFamily { get; set; } = ""; public double ScoreWeight { get; set; } = 1.0; public double ScoreUptime { get; set; } = 1.0; public double ScoreCap { get; set; } = 100000.0; public double ScoreEventSeconds { get; set; } public double ScoreEvery { get; set; } = 1.0; public double ScoreDuration { get; set; } public bool ScoreLocal { get; set; } = true; } public sealed class FateGenerator { private readonly GameplayConfig config; private readonly IRandomSource rarityRandom; private readonly IRandomSource selectionRandom; private readonly IRandomSource cosmeticRandom; private readonly StrengthSampler strength; private readonly PerkSelector selector; private readonly QualityProbability qualityProbability; private readonly string configurationFingerprint; private readonly Dictionary<string, Dictionary<string, double>?> inclusionCache = new Dictionary<string, Dictionary<string, double>>(StringComparer.Ordinal); private readonly Dictionary<string, List<string>> validationCache = new Dictionary<string, List<string>>(StringComparer.Ordinal); private readonly object gate = new object(); public FateGenerator(GameplayConfig config, IRandomSource? rarityRng = null, IRandomSource? selectionRng = null, IRandomSource? strengthRng = null, IRandomSource? cosmeticRng = null) { List<string> list = ConfigurationValidator.Validate(config); if (list.Count != 0) { throw new ArgumentException(string.Join("\n", list)); } this.config = JsonConvert.DeserializeObject<GameplayConfig>(JsonConvert.SerializeO
BepInEx/plugins/Nornforged/Nornforged.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Nornforged.Audio; using Nornforged.Compatibility; using Nornforged.Core; using Nornforged.Crafting; using Nornforged.Effects; using Nornforged.Forging; using Nornforged.Items; using Nornforged.Networking; using Nornforged.UI; using Nornforged.Visuals; using TMPro; using Unity.Collections; using UnityEngine; using UnityEngine.Audio; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem.UI; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Rinor")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Permanent, independently rolled equipment Fateprints for Valheim.")] [assembly: AssemblyFileVersion("0.5.4.0")] [assembly: AssemblyInformationalVersion("0.5.4")] [assembly: AssemblyProduct("Nornforged")] [assembly: AssemblyTitle("Nornforged")] [assembly: AssemblyVersion("0.5.4.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 Nornforged { [BepInPlugin("rinor.nornforged", "Nornforged — Fate at the Forge", "0.5.4")] [BepInDependency("com.jotunn.jotunn", "2.30.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "rinor.nornforged"; public const string PluginName = "Nornforged — Fate at the Forge"; public const string PluginVersion = "0.5.4"; private Harmony? _harmony; private NornPresentation? _presentation; private ConfigEntry<float> _intensity; private ConfigEntry<bool> _trails; private ConfigEntry<bool> _particles; private ConfigEntry<bool> _reduced; private ConfigEntry<bool> _crafter; private ConfigEntry<bool> _inventoryAnimation; private ConfigEntry<bool> _sounds; private ConfigEntry<bool> _dropSounds; private ConfigEntry<bool> _perkAnimation; private ConfigEntry<bool> _scoreBadges; private ConfigEntry<bool> _scoreAnimation; private ConfigEntry<bool> _perkCounts; private ConfigEntry<float> _soundVolume; private ConfigEntry<bool> _equipmentSounds; private ConfigEntry<float> _equipmentSoundVolume; private ZNet? _session; private float _nextDiagnostics; private string _startupConfig = ""; private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { TypeNameHandling = (TypeNameHandling)0, MaxDepth = 32, MissingMemberHandling = (MissingMemberHandling)1 }; public static Plugin? Current { get; private set; } public static GameplayConfig Settings { get; private set; } = new GameplayConfig { Perks = Registry.CreateDefault(true) }; public static IReadOnlyList<PerkDefinition> Registry => Settings.Perks; public static FateGenerator Generator { get; private set; } = null; public static string ConfigurationProblem { get; private set; } = ""; public static string SettingsPath => Path.Combine(Paths.ConfigPath, "Nornforged", "gameplay.json"); private static string DataRoot => Path.GetDirectoryName(Path.GetFullPath(Paths.ConfigPath)); public static string JournalDirectory => Path.Combine(DataRoot, "NornforgedData", "authority"); public static string AuthorityBackupDirectory => Path.Combine(DataRoot, "NornforgedBackups", "authority"); public static string LegacyJournalDirectory => Path.Combine(Paths.ConfigPath, "Nornforged", "authority"); private void Awake() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Expected O, but got Unknown //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047d: Expected O, but got Unknown Current = this; _intensity = ((BaseUnityPlugin)this).Config.Bind<float>("Visuals", "Intensity", 0.8f, new ConfigDescription("Client-only intensity for elemental weapon and armor effects on every equipment rank. Perks with at least 25.00% quality qualify; saved perk quality and amount determine each effect's strength. Zero hides world effects.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); _trails = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "Trails", true, "Allow short world-space ember, frost and lightning accent trails. Disabling keeps accents attached to equipment; teleports and hidden equipment clear old particles."); _particles = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "Particles", true, "Show authored surfaces and motion on equipment with qualifying perks (25.00% PQS or higher), independent of rarity. The two strongest visual families define the effect. Disabling hides all added world effects; inventory icons are unchanged."); _reduced = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "ReducedEffects", false, "Use fewer, slower elemental particles and disable activation pulses, UI shaking, animated perk-name mist and rolling effects. Static perk colours remain visible; forge results appear immediately."); _crafter = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "ShowCrafter", true, "Show the saved original crafter in F6 details. Compact tooltips retain Valheim's own crafter line without adding a duplicate."); _inventoryAnimation = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "AnimatedRarityBackgrounds", true, "Smooth rarity transitions on native item-slot backgrounds during a forge reveal. Native transparency is preserved. Independent of Gear Score badge animation."); _scoreBadges = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "ShowGearScoreBadges", true, "Show a compact framed Gear Score with up to two decimal places on inventory and quickslot equipment icons. Native hotkeys, quality and durability remain visible. No badges on materials, food, drag cursors or the forge socket."); _perkCounts = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "ShowPerkCounts", true, "Show a small signed perk count in rarity colour above the Gear Score on inventory and quickslot equipment icons. Independent of ShowGearScoreBadges. The forge socket shows only the revealed count; Default, untrusted items and drag cursors have no count."); _scoreAnimation = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "AnimatedGearScoreBadges", true, "Gently animate high-score icon light (500/750/1000) and the distinct Transcendent glints. Disable for static badges and light. ReducedEffects also disables this motion."); _perkAnimation = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "AnimatedPerkNames", true, "Show slow, subtle element-coloured mist behind perk names above 50.00% saved PQS. Name colour always blends from white at 0.00% to full element colour at 50.00%. This setting and ReducedEffects disable the mist, without changing the static colour or perk values."); _sounds = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "RaritySounds", true, "Play original rarity-tier chimes on a newly revealed Fateprint."); _dropSounds = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "GroundDropSounds", true, "Play a positional rarity sound when rolled equipment is actually dropped. Loading stored ground items is silent."); _soundVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "Volume", 0.45f, new ConfigDescription("Rarity sound volume, also respecting the game's audio mixer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); _equipmentSounds = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "EquipmentSounds", true, "Add matching spatial swing and impact accents for weapons with qualifying perk VFX. Original weapon sounds remain. Independent of rarity sounds."); _equipmentSoundVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "EquipmentVolume", 0.35f, new ConfigDescription("Volume of additional perk weapon sounds, respecting Valheim's SFX mixer. Zero mutes these accents.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); Directory.CreateDirectory(Path.GetDirectoryName(SettingsPath)); try { if (!File.Exists(SettingsPath)) { File.WriteAllText(SettingsPath, JsonConvert.SerializeObject((object)Settings, (Formatting)1)); } _startupConfig = File.ReadAllText(SettingsPath); GameplayConfigUpgradeResult val = GameplayConfigUpgrade.UpgradeUntouchedDefaults(_startupConfig); if (val.Upgraded) { string text = SettingsPath + "." + Guid.NewGuid().ToString("N") + ".tmp"; string text2 = SettingsPath + "." + val.FromVersion + "." + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + ".bak"; File.WriteAllText(text, val.Json); File.Replace(text, SettingsPath, text2); _startupConfig = val.Json; ((BaseUnityPlugin)this).Logger.LogInfo((object)(val.Diagnostic + " Backup: " + text2)); } else if (val.FromVersion.Length == 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)val.Diagnostic); } ApplyConfiguration(_startupConfig); } catch (Exception ex) { ConfigurationProblem = "Invalid Nornforged gameplay configuration: " + ex.Message; LogError(ConfigurationProblem); } NetworkAuthority.Initialize("0.5.4", JournalDirectory, () => Settings, ApplyConfiguration, LogWarning, AuthorityBackupDirectory, LegacyJournalDirectory); CraftingService.Initialize(delegate(ItemData item) { FateUi.Reveal(item); if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { EffectRuntime.Invalidate(Player.m_localPlayer); } }); ForgeService.Initialize(); EffectRuntime.Activated = (Action<Player, string>)Delegate.Combine(EffectRuntime.Activated, new Action<Player, string>(FateVisuals.Activate)); _harmony = new Harmony("rinor.nornforged"); try { _harmony.PatchAll(typeof(Plugin).Assembly); InventoryCompatibility.Install(_harmony, LogInfo); ToolModInterop.Install(_harmony); } catch (Exception ex2) { ConfigurationProblem = "Nornforged game hooks did not initialize: " + ex2.Message; LogError(ex2.ToString()); throw; } RegisterCommands(); _presentation = new NornPresentation(((BaseUnityPlugin)this).Config); ForgeController.Configure(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Nornforged — Fate at the Forge 0.5.4. Tested: Valheim 1.0.16, BepInEx 5.4.23.5, Jotunn 2.30.2. Inventory: Fate forge; F6 Fateprint; F9 damage ledger."); } public static void ApplyConfiguration(string json) { //IL_053e: Unknown result type (might be due to invalid IL or missing references) //IL_054e: Expected O, but got Unknown //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) if (json.Length > 2097152) { throw new InvalidDataException("Gameplay configuration exceeds 2 MiB."); } GameplayConfig val = JsonConvert.DeserializeObject<GameplayConfig>(json, JsonSettings) ?? throw new InvalidDataException("Missing configuration object."); if (val.PerkPlacementVersion < 0 || val.PerkPlacementVersion > 1) { throw new InvalidDataException("Unsupported perk placement policy version."); } val.PerkPlacementVersion = 1; List<string> list = ConfigurationValidator.Validate(val, (IEnumerable<ItemProfile>)Registry.ValidationProfiles(), true); if (list.Count > 0) { throw new InvalidDataException(string.Join("\n", list)); } Dictionary<string, PerkDefinition> dictionary = Registry.CreateDefault(true).ToDictionary<PerkDefinition, string>((PerkDefinition d) => d.Id, StringComparer.Ordinal); foreach (PerkDefinition perk in val.Perks) { if (!dictionary.TryGetValue(perk.Id, out var value) || perk.Effect != value.Effect || perk.DefinitionVersion != value.DefinitionVersion) { list.Add("Unsupported effect ID or definition version: " + perk.Id); continue; } if (!(from val4 in perk.Parameters select val4.Id into x orderby x select x).SequenceEqual(from val4 in value.Parameters select val4.Id into x orderby x select x)) { list.Add("Effect parameters cannot be renamed or removed: " + perk.Id); } if (perk.Class != value.Class) { list.Add("A perk's execution class cannot be changed: " + perk.Id); } foreach (PerkParameter p in perk.Parameters) { PerkParameter val2 = ((IEnumerable<PerkParameter>)value.Parameters).FirstOrDefault((Func<PerkParameter, bool>)((PerkParameter x) => x.Id == p.Id)); if (p.Min < 0.0 || p.Max > (double)((p.Unit == "s") ? 86400 : ((p.Unit == "m") ? 32 : ((p.Unit == "%") ? 3000 : 1000)))) { list.Add("Parameter outside supported safety bounds: " + perk.Id + "." + p.Id); } if (val2 != null && (p.Unit != val2.Unit || p.HigherIsBetter != val2.HigherIsBetter)) { list.Add("Parameter units/direction are execution contracts: " + perk.Id + "." + p.Id); } if (((p.Id == "chance" || perk.Id == "aegirs_favor" || perk.Id == "bait_keeper" || perk.Id == "hooked_fortune") && p.Max > 100.0) || (perk.Id == "eternal_temper" && p.Max > 99.0)) { list.Add("Trigger chance exceeds the fixed safety limit: " + perk.Id); } } if (PerkEligibility.HasMissingExecutionCapabilities(value, perk) || value.ExcludedNativeEffects.Except(perk.ExcludedNativeEffects).Any() || value.ExclusionGroups.Except(perk.ExclusionGroups).Any() || value.IncompatibleIds.Except(perk.IncompatibleIds).Any()) { list.Add("Required compatibility or exclusion rule removed: " + perk.Id); } if (PerkEligibility.HasUnsupportedCategoryExpansion(value, perk)) { list.Add("Unsupported category expansion: " + perk.Id); } } foreach (ItemOverride itemOverride in val.ItemOverrides) { if (!Registry.Categories.Contains(itemOverride.Category) && itemOverride.Enabled) { list.Add("Unknown item override category: " + itemOverride.Category); } } if (list.Count > 0) { throw new InvalidDataException(string.Join("\n", list)); } FateGenerator val3 = new FateGenerator(val, (IRandomSource)null, (IRandomSource)null, (IRandomSource)null, (IRandomSource)null); Settings = val; Generator = val3; ConfigurationProblem = ""; Classification.ConfigProvider = () => Settings; if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { EffectRuntime.Invalidate(Player.m_localPlayer); } } private void Update() { if ((Object)(object)_session != (Object)(object)ZNet.instance) { _session = ZNet.instance; if (Object.op_Implicit((Object)(object)_session) && _session.IsServer()) { try { ApplyConfiguration(_startupConfig); } catch (Exception ex) { ConfigurationProblem = ex.Message; } } } NetworkAuthority.Update(); ProcessorFateService.Update(); CraftingService.Update(); ForgeService.Update(); _presentation?.Tick(); if (!Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.IsDedicated()) { ForgeController.Tick(blocked: true); FateHoverController.Tick(blocked: true); return; } FateVisuals.Intensity = _intensity.Value; FateVisuals.Trails = _trails.Value; FateVisuals.Particles = _particles.Value; FateVisuals.Reduced = _reduced.Value; FateIcon.AnimatedBackgrounds = _inventoryAnimation.Value; FateIcon.ReducedMotion = _reduced.Value; GearScoreIcon.ShowBadges = _scoreBadges.Value; GearScoreIcon.AnimateBadges = _scoreAnimation.Value; GearScoreIcon.ShowPerkCounts = _perkCounts.Value; FateHoverController.AnimatedQuality = _perkAnimation.Value; FateAudio.Enabled = _sounds.Value; FateAudio.GroundDrops = _dropSounds.Value; FateAudio.Volume = _soundVolume.Value; EquipmentAudio.Enabled = _equipmentSounds.Value; EquipmentAudio.Volume = _equipmentSoundVolume.Value; FateUi.ShowCrafter = _crafter.Value; ForgeController.Tick(_presentation?.IsOpen ?? false); NornPresentation? presentation = _presentation; if ((presentation == null || !presentation.IsOpen) && !ForgeController.IsOpen) { FateUi.Tick(); } else { FateUi.Close(); } FateHoverController.Tick(_presentation?.IsOpen ?? false); if (ConfigurationProblem.Length > 0 && Object.op_Implicit((Object)(object)Player.m_localPlayer) && Time.unscaledTime > _nextDiagnostics) { _nextDiagnostics = Time.unscaledTime + 30f; ((Character)Player.m_localPlayer).Message((MessageType)1, ConfigurationProblem, 0, (Sprite)null, false); } } private void OnDestroy() { EquipmentAudio.Shutdown(); ForgeController.Shutdown(); ForgeCraftCelebration.Shutdown(); ForgeService.Shutdown(); _presentation?.Dispose(); EquipmentComparisonController.Shutdown(); FateHoverController.Shutdown(); GearScoreInventoryWidget.Shutdown(); GearScoreIcon.Shutdown(); FateIcon.Shutdown(); FateAudio.Shutdown(); ForgeRevealAudio.Shutdown(); EffectRuntime.Activated = (Action<Player, string>)Delegate.Remove(EffectRuntime.Activated, new Action<Player, string>(FateVisuals.Activate)); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } FateVisuals.Shutdown(); NornBrandAssets.Clear(); Current = null; } private void RegisterCommands() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand("nornforged", "Nornforged: status | inspect | validate | simulate [count] | damage | welcome | forge | recover. No command awards equipment.", (ConsoleEvent)delegate(ConsoleEventArgs args) { //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Expected O, but got Unknown switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "status") { case "recover": args.Context.AddString(CraftingService.RecoverLegacyPending()); break; case "forge": ForgeController.RequestOpen(); args.Context.AddString("Close the console and open your inventory to view the Fate forge. No action starts automatically."); break; case "welcome": _presentation?.RequestWelcome(); args.Context.AddString("The Nornforged guide opens in the current world."); break; case "damage": _presentation?.OpenDetails(); break; case "status": args.Context.AddString(string.Format("Nornforged {0}; authority ready={1}; definitions={2}; {3}", "0.5.4", NetworkAuthority.Ready, Registry.Count, ConfigurationProblem)); args.Context.AddString(NetworkAuthority.StatusMessage); if (NetworkAuthority.IsAuthority) { args.Context.AddString("Authority data: " + JournalDirectory); args.Context.AddString("Authority backups: " + AuthorityBackupDirectory); } break; case "inspect": { Player localPlayer = Player.m_localPlayer; ItemData val2 = FateUi.InspectionItem ?? (Object.op_Implicit((Object)(object)localPlayer) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null); if (val2 == null) { args.Context.AddString("Hover equipment in an inventory or equip it first."); } else { Fateprint val3 = ItemFate.Read(val2); args.Context.AddString((val3 == null) ? ItemFate.Diagnose(val2).Message : FateUi.Describe(val3, expanded: true)); } break; } case "validate": { List<string> list = ConfigurationValidator.Validate(Settings, (IEnumerable<ItemProfile>)Registry.ValidationProfiles(), true); if (Object.op_Implicit((Object)(object)ObjectDB.instance)) { foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (Object.op_Implicit((Object)(object)recipe) && Object.op_Implicit((Object)(object)recipe.m_item)) { ItemProfile val4 = Classification.Describe(recipe.m_item.m_itemData); if (val4 != null) { list.AddRange(Generator.Validate(val4)); } } } } args.Context.AddString((list.Count == 0) ? "All configured and currently loaded craftable equipment pools validate." : string.Join("\n", list)); break; } case "simulate": { int result = 10000; if (args.Length > 2 && !int.TryParse(args[2], out result)) { result = 10000; } result = Math.Max(1, Math.Min(1000000, result)); DeterministicRandomSource val = new DeterministicRandomSource(1313821262uL); Dictionary<string, int> dictionary = Settings.Rarities.ToDictionary((RarityDefinition r) => r.Id, (RarityDefinition _) => 0); for (int num = 0; num < result; num++) { dictionary[WeightedRandom.Select<RarityDefinition>((IReadOnlyList<RarityDefinition>)Settings.Rarities, (Func<RarityDefinition, int>)((RarityDefinition r) => r.Weight), (IRandomSource)(object)val).Id]++; } args.Context.AddString("Isolated deterministic rarity simulation; does not consume gameplay RNG:"); { foreach (RarityDefinition rarity in Settings.Rarities) { args.Context.AddString($"{rarity.Name}: {dictionary[rarity.Id]}/{result} = {PercentageText.Format(100.0 * (double)dictionary[rarity.Id] / (double)result, false)} (configured {PercentageText.Format((double)rarity.Weight / 100.0, false)})"); } break; } } default: args.Context.AddString("Use nornforged status, inspect, validate, simulate [1..1000000], damage, welcome, or recover."); break; } }, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } public static void LogInfo(string message) { Plugin? current = Current; if (current != null) { ((BaseUnityPlugin)current).Logger.LogInfo((object)message); } } public static void LogWarning(string message) { Plugin? current = Current; if (current != null) { ((BaseUnityPlugin)current).Logger.LogWarning((object)message); } } public static void LogError(string message) { Plugin? current = Current; if (current != null) { ((BaseUnityPlugin)current).Logger.LogError((object)message); } } } } namespace Nornforged.Visuals { public static class AuthoredVfxAssets { private static AssetBundle? bundle; private static Shader? surfaceShader; private static Shader? particleShader; private static Material? surfaceMaterial; private static readonly Dictionary<int, Material> ParticleMaterials = new Dictionary<int, Material>(); private static bool attempted; private static Texture? flameAtlas; public static bool IsReady => EnsureLoaded(); public static string Diagnostic { get; private set; } = "Not loaded"; public static int SharedMaterialCount => (Object.op_Implicit((Object)(object)surfaceMaterial) ? 1 : 0) + ParticleMaterials.Count; public static int SharedTextureCount => Object.op_Implicit((Object)(object)flameAtlas) ? 1 : 0; public static string SelectedShader { get { if (!Object.op_Implicit((Object)(object)surfaceShader) || !Object.op_Implicit((Object)(object)particleShader)) { return Diagnostic; } return ((Object)surfaceShader).name + "; " + ((Object)particleShader).name; } } public static int[] DominantChannels(FateVisualStyle style) { return style.VisibleChannels.ToArray(); } private static bool EnsureLoaded() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) if (attempted) { if (Object.op_Implicit((Object)(object)surfaceShader)) { return Object.op_Implicit((Object)(object)particleShader); } return false; } if (!FateVisuals.Available) { return false; } attempted = true; try { string text = (((int)Application.platform == 13) ? "linux" : (((int)Application.platform == 2 || (int)Application.platform == 7) ? "windows" : "")); if (text.Length == 0) { throw new NotSupportedException("Authored VFX currently support Windows and Linux players."); } using (Stream stream = typeof(AuthoredVfxAssets).Assembly.GetManifestResourceStream("Nornforged.Vfx." + text + ".bundle")) { if (stream == null) { throw new FileNotFoundException("The authored " + text + " VFX bundle is missing from this build."); } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); bundle = AssetBundle.LoadFromMemory(memoryStream.ToArray()); } if (!Object.op_Implicit((Object)(object)bundle)) { throw new InvalidDataException("Unity could not load the authored VFX bundle."); } surfaceShader = bundle.LoadAsset<Shader>("Assets/Nornforged/Shaders/ElementalSurface.shader"); particleShader = bundle.LoadAsset<Shader>("Assets/Nornforged/Shaders/ElementalParticle.shader"); if (!Object.op_Implicit((Object)(object)surfaceShader) || !Object.op_Implicit((Object)(object)particleShader) || !surfaceShader.isSupported || !particleShader.isSupported) { throw new NotSupportedException("The authored VFX shaders are unavailable for " + ((object)SystemInfo.graphicsDeviceType/*cast due to .constrained prefix*/).ToString() + " (surface=" + (Object.op_Implicit((Object)(object)surfaceShader) ? surfaceShader.isSupported.ToString() : "missing") + ", particles=" + (Object.op_Implicit((Object)(object)particleShader) ? particleShader.isSupported.ToString() : "missing") + ")."); } Diagnostic = "Authored VFX ready: " + text + " / " + ((object)SystemInfo.graphicsDeviceType/*cast due to .constrained prefix*/).ToString(); return true; } catch (Exception ex) { Diagnostic = ex.Message; Plugin.LogWarning("Equipment VFX: " + Diagnostic); surfaceShader = (particleShader = null); if (Object.op_Implicit((Object)(object)bundle)) { bundle.Unload(true); } bundle = null; return false; } } public static Material? CreateSurfaceMaterial(FateVisualStyle style, int seed, bool armor) { //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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if (!EnsureLoaded()) { return null; } if (!Object.op_Implicit((Object)(object)surfaceMaterial)) { surfaceMaterial = new Material(surfaceShader) { name = "Nornforged authored equipment surface", hideFlags = (HideFlags)52 }; } return surfaceMaterial; } public static void ApplySurfaceStyle(MaterialPropertyBlock block, FateVisualStyle style, int seed, bool armor) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) int[] array = DominantChannels(style); int num = ((array.Length != 0) ? array[0] : 5); int num2 = ((array.Length > 1) ? array[1] : num); block.SetColor("_Tint", style.ElementColor(num)); block.SetColor("_SecondaryTint", style.ElementColor(num2)); block.SetFloat("_Element", (float)num); block.SetFloat("_SecondaryElement", (float)num2); block.SetFloat("_Strength", style.ElementStrength(num)); block.SetFloat("_SecondaryStrength", (array.Length > 1) ? style.ElementStrength(num2) : 0f); block.SetFloat("_AccentWeight", (float)style.Composition.SecondaryWeight); block.SetFloat("_Seed", (float)((uint)seed % 10007u) * 0.0137f); block.SetFloat("_SurfaceMode", (float)(armor ? 1 : 0)); } public static Material? CreateParticleMaterial(int element, bool accent) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if (!EnsureLoaded()) { return null; } int key = element * 2 + (accent ? 1 : 0); if (ParticleMaterials.TryGetValue(key, out Material value) && Object.op_Implicit((Object)(object)value)) { value.SetFloat("_Reduced", (float)(FateVisuals.Reduced ? 1 : 0)); return value; } Material val = new Material(particleShader) { name = "Nornforged authored " + element + (accent ? " sparks" : " flow"), hideFlags = (HideFlags)52 }; val.SetFloat("_Element", (float)element); val.SetFloat("_Accent", (float)(accent ? 1 : 0)); val.SetFloat("_Reduced", (float)(FateVisuals.Reduced ? 1 : 0)); if (element == 0 && !accent && Object.op_Implicit((Object)(object)ObjectDB.instance)) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("SwordDyrnwyn"); Transform val2 = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.transform.Find("attach/Burny vfx/fx_Torch_Basic") : null); object obj; if (!Object.op_Implicit((Object)(object)val2)) { obj = null; } else { ParticleSystemRenderer component = ((Component)val2).GetComponent<ParticleSystemRenderer>(); obj = ((component != null) ? ((Renderer)component).sharedMaterial : null); } Material val3 = (Material)obj; if (Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val3.mainTexture)) { flameAtlas = val3.mainTexture; val.SetTexture("_MainTex", flameAtlas); val.SetFloat("_NativeFlame", 1f); } } ParticleMaterials[key] = val; return val; } public static void Shutdown() { if (Object.op_Implicit((Object)(object)surfaceMaterial)) { Object.Destroy((Object)(object)surfaceMaterial); } foreach (Material value in ParticleMaterials.Values) { if (Object.op_Implicit((Object)(object)value)) { Object.Destroy((Object)(object)value); } } ParticleMaterials.Clear(); surfaceMaterial = null; flameAtlas = null; if (Object.op_Implicit((Object)(object)bundle)) { bundle.Unload(true); } bundle = null; surfaceShader = (particleShader = null); attempted = false; Diagnostic = "Not loaded"; } } [DefaultExecutionOrder(11000)] public sealed class ElementalVfx : MonoBehaviour { private sealed class Channel { internal int Element; internal int Capacity; internal int Serial; internal bool Accent; internal bool World; internal ParticleSystem System; internal ParticleSystemRenderer Renderer; internal Particle[] Particles = Array.Empty<Particle>(); internal float Fraction; internal float Rate; internal float Lifetime; internal float Size; internal float Strength; } private static readonly HashSet<ElementalVfx> Live = new HashSet<ElementalVfx>(); private readonly List<Channel> channels = new List<Channel>(); private readonly List<ParticleSystem> systems = new List<ParticleSystem>(); private Vector3[] anchors = Array.Empty<Vector3>(); private Vector3[] normals = Array.Empty<Vector3>(); private Vector3[] previousAnchors = Array.Empty<Vector3>(); private FateVisualStyle style = new FateVisualStyle(); private float scale = 1f; private float pulseUntil; private bool armor; private bool visible = true; private bool configured; private bool reduced; private int seed; private int budget; private int setKey; private string signature = ""; private Matrix4x4 simulationStretch; private Vector3 lastPosition; public bool HasVisual { get { if (configured) { return channels.Count > 0; } return false; } } public int ParticleCapacity => channels.Sum((Channel c) => c.Capacity); public int ChannelCount => channels.Select((Channel c) => c.Element).Distinct().Count(); public IReadOnlyList<ParticleSystem> Systems => systems; public IReadOnlyList<Vector3> Anchors => anchors; public float EffectivePower { get { if (configured) { return Mathf.Clamp01(FateVisuals.Intensity) * (0.15f + 2.35f * style.PeakStrength) * PulsePower; } return 0f; } } private float PulsePower { get { if (!reduced) { return 1f + 0.14f * Mathf.SmoothStep(0f, 1f, Mathf.Clamp01((pulseUntil - Time.unscaledTime) / 0.65f)); } return 1f; } } public static int SharedMaterialCount => AuthoredVfxAssets.SharedMaterialCount; public static int SharedTextureCount => AuthoredVfxAssets.SharedTextureCount; public static string SelectedShader => AuthoredVfxAssets.SelectedShader; public static bool IsOwned(Renderer renderer) { if (Object.op_Implicit((Object)(object)renderer)) { return Object.op_Implicit((Object)(object)((Component)renderer).GetComponentInParent<ElementalVfxMarker>(true)); } return false; } public void SetBudget(int particles) { budget = Mathf.Clamp(particles, 4, 144); } public void Configure(int rank, int seed, FateVisualStyle style, Vector3[] anchors, float scale, bool armor, int? setKey = null) { //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) if (!style.IsVisible || FateVisuals.Intensity <= 0f || !FateVisuals.Particles || !FateVisuals.Available) { ClearVisual(); return; } this.seed = seed; this.style = style; this.armor = armor; this.scale = Mathf.Clamp(scale, 0.25f, 1.5f); this.setKey = setKey ?? seed; string text = seed + ":" + style.Signature + ":" + armor + ":" + budget + ":" + this.setKey + ":" + FateVisuals.Revision; UpdateAnchors(anchors); if (configured && signature == text) { SetVisible(value: true); return; } ClearVisual(); signature = text; reduced = FateVisuals.Reduced; visible = true; int[] array = AuthoredVfxAssets.DominantChannels(style); if (array.Length == 0 || !AuthoredVfxAssets.IsReady) { return; } int num = Math.Min((budget > 0) ? budget : (armor ? 48 : 144), armor ? 24 : 72); if (reduced) { num = Math.Min(num, armor ? 14 : 28); } int num2 = Mathf.Clamp(Mathf.RoundToInt((float)num * (0.23f + 0.77f * style.PeakStrength)), Math.Min(num, array.Length * 2), num); int num3 = ((array.Length > 1) ? Mathf.Clamp(Mathf.RoundToInt((float)num2 * (float)style.Composition.SecondaryWeight), 1, num2 - 1) : 0); for (int i = 0; i < array.Length; i++) { int num4 = ((i == 0) ? (num2 - num3) : num3); int num5 = ((!reduced && style.ElementStrength(array[i]) >= 0.25f && num4 >= 6 && array[i] < 5) ? Math.Max(1, num4 / 5) : 0); AddChannel(array[i], accent: false, num4 - num5); if (num5 > 0) { AddChannel(array[i], accent: true, num5); } } configured = channels.Count > 0; ((Behaviour)this).enabled = configured; lastPosition = ((Component)this).transform.position; if (configured) { Live.Add(this); WarmUp(0.55f); } } public void UpdateAnchors(Vector3[] positions, Vector3[]? directions = null) { //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) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) anchors = ((positions != null && positions.Length != 0) ? positions : Array.Empty<Vector3>()); normals = ((directions != null && directions.Length == anchors.Length) ? directions : Array.Empty<Vector3>()); if (previousAnchors.Length != anchors.Length) { previousAnchors = (Vector3[])anchors.Clone(); } if (anchors.Length == 0) { return; } Bounds localBounds = default(Bounds); ((Bounds)(ref localBounds))..ctor(anchors[0], Vector3.zero); for (int i = 1; i < anchors.Length; i++) { ((Bounds)(ref localBounds)).Encapsulate(anchors[i]); } Vector3 val = ((Component)this).transform.InverseTransformVector(Vector3.one * 0.65f); float magnitude = ((Vector3)(ref val)).magnitude; ((Bounds)(ref localBounds)).Expand(Mathf.Max(0.001f, magnitude)); foreach (Channel channel in channels) { if (Object.op_Implicit((Object)(object)channel.Renderer)) { ((Renderer)channel.Renderer).localBounds = localBounds; } } } private void AddChannel(int element, bool accent, int capacity) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_0431: Unknown result type (might be due to invalid IL or missing references) if (capacity < 1) { return; } Material val = AuthoredVfxAssets.CreateParticleMaterial(element, accent); if (Object.op_Implicit((Object)(object)val)) { GameObject val2 = new GameObject("Nornforged authored " + element + (accent ? " sparks" : " flow")); val2.SetActive(false); val2.transform.SetParent(((Component)this).transform, false); val2.layer = ((Component)this).gameObject.layer; val2.AddComponent<ElementalVfxMarker>(); ParticleSystem val3 = val2.AddComponent<ParticleSystem>(); val3.Stop(false, (ParticleSystemStopBehavior)0); bool flag = accent && element < 3 && FateVisuals.Trails && !reduced; MainModule main = val3.main; ((MainModule)(ref main)).loop = true; ((MainModule)(ref main)).duration = 5f; ((MainModule)(ref main)).prewarm = false; ((MainModule)(ref main)).startDelay = MinMaxCurve.op_Implicit(0f); ((MainModule)(ref main)).playOnAwake = false; ((MainModule)(ref main)).maxParticles = capacity; ((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)(flag ? 1 : 0); ((MainModule)(ref main)).scalingMode = (ParticleSystemScalingMode)2; ((MainModule)(ref main)).simulationSpeed = 1f; ((MainModule)(ref main)).cullingMode = (ParticleSystemCullingMode)3; ((MainModule)(ref main)).startSpeed = MinMaxCurve.op_Implicit(0f); ((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(0f); ((MainModule)(ref main)).startSize3D = false; ((MainModule)(ref main)).startRotation3D = false; EmissionModule emission = val3.emission; ((EmissionModule)(ref emission)).enabled = false; ShapeModule shape = val3.shape; ((ShapeModule)(ref shape)).enabled = false; VelocityOverLifetimeModule velocityOverLifetime = val3.velocityOverLifetime; ((VelocityOverLifetimeModule)(ref velocityOverLifetime)).enabled = false; ForceOverLifetimeModule forceOverLifetime = val3.forceOverLifetime; ((ForceOverLifetimeModule)(ref forceOverLifetime)).enabled = false; InheritVelocityModule inheritVelocity = val3.inheritVelocity; ((InheritVelocityModule)(ref inheritVelocity)).enabled = false; ColorOverLifetimeModule colorOverLifetime = val3.colorOverLifetime; Gradient val4 = new Gradient(); val4.SetKeys((GradientColorKey[])(object)new GradientColorKey[2] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) }, (GradientAlphaKey[])(object)new GradientAlphaKey[4] { new GradientAlphaKey(0f, 0f), new GradientAlphaKey(1f, (element == 2) ? 0.04f : 0.13f), new GradientAlphaKey(0.65f, 0.55f), new GradientAlphaKey(0f, 1f) }); ((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled = true; ((ColorOverLifetimeModule)(ref colorOverLifetime)).color = MinMaxGradient.op_Implicit(val4); SizeOverLifetimeModule sizeOverLifetime = val3.sizeOverLifetime; ((SizeOverLifetimeModule)(ref sizeOverLifetime)).enabled = true; ((SizeOverLifetimeModule)(ref sizeOverLifetime)).size = new MinMaxCurve(1f, new AnimationCurve((Keyframe[])(object)new Keyframe[3] { new Keyframe(0f, 0.45f), new Keyframe(0.2f, 1f), new Keyframe(1f, accent ? 0.1f : 0.65f) })); LimitVelocityOverLifetimeModule limitVelocityOverLifetime = val3.limitVelocityOverLifetime; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).enabled = false; NoiseModule noise = val3.noise; ((NoiseModule)(ref noise)).enabled = element != 2 && element != 5 && element != 6 && element != 10; ((NoiseModule)(ref noise)).strength = MinMaxCurve.op_Implicit((element == 0) ? 0.035f : 0.012f); ((NoiseModule)(ref noise)).frequency = ((element == 0) ? 2.2f : 0.8f); ((NoiseModule)(ref noise)).scrollSpeed = MinMaxCurve.op_Implicit((element == 0) ? 0.7f : 0.2f); ((NoiseModule)(ref noise)).damping = true; ((NoiseModule)(ref noise)).quality = (ParticleSystemNoiseQuality)0; LightsModule lights = val3.lights; ((LightsModule)(ref lights)).enabled = false; TrailModule trails = val3.trails; ((TrailModule)(ref trails)).enabled = false; float num = style.ElementStrength(element); float num2 = element switch { 2 => accent ? 0.2f : 0.18f, 1 => accent ? 0.55f : 1.05f, 0 => accent ? 0.38f : 0.48f, _ => 0.75f, }; if (element == 0 && !accent && val.GetFloat("_NativeFlame") > 0.5f) { TextureSheetAnimationModule textureSheetAnimation = val3.textureSheetAnimation; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).enabled = true; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).mode = (ParticleSystemAnimationMode)0; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).numTilesX = 8; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).numTilesY = 8; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).animation = (ParticleSystemAnimationType)0; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).timeMode = (ParticleSystemAnimationTimeMode)2; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).fps = 45f; ((TextureSheetAnimationModule)(ref textureSheetAnimation)).startFrame = new MinMaxCurve(0f, 1f); num2 = 0.65f; } float num3 = element switch { 2 => 0.1f + 0.14f * num, 1 => 0.1f + 0.16f * num, 0 => 0.13f + 0.18f * num, _ => 0.1f + 0.15f * num, }; if (element == 5 || element == 6) { num3 = 0.02f + 0.016f * num; } if (element == 7 || element == 9 || element == 10) { num3 = 0.04f + 0.045f * num; } if (element == 8 || element == 11) { num3 = 0.09f + 0.11f * num; } float num4 = ((!accent) ? num3 : ((element == 1) ? 0.018f : 0.012f)) * Mathf.Lerp(0.7f, 1.05f, scale / 1.5f) * (armor ? 0.7f : 1f); if (element == style.Composition.SecondaryChannel) { num4 *= 0.85f; } ParticleSystemRenderer component = ((Component)val3).GetComponent<ParticleSystemRenderer>(); ((Renderer)component).sharedMaterial = val; component.renderMode = (ParticleSystemRenderMode)0; component.alignment = (ParticleSystemRenderSpace)0; component.SetActiveVertexStreams(new List<ParticleSystemVertexStream> { (ParticleSystemVertexStream)0, (ParticleSystemVertexStream)3, (ParticleSystemVertexStream)4 }); ((Renderer)component).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)component).receiveShadows = false; ((Renderer)component).allowOcclusionWhenDynamic = false; component.minParticleSize = 0f; component.maxParticleSize = 0.18f; ((Renderer)component).lightProbeUsage = (LightProbeUsage)0; ((Renderer)component).reflectionProbeUsage = (ReflectionProbeUsage)0; ((Renderer)component).motionVectorGenerationMode = (MotionVectorGenerationMode)2; Channel item = new Channel { Element = element, Accent = accent, World = flag, Capacity = capacity, System = val3, Renderer = component, Lifetime = num2, Size = num4, Strength = num, Rate = (float)capacity / num2 * ((element >= 5) ? 0.25f : 0.42f), Particles = (Particle[])(object)new Particle[capacity] }; channels.Add(item); systems.Add(val3); val3.useAutoRandomSeed = false; val3.randomSeed = (uint)((seed + element * 919 + ((!accent) ? 1 : 71)) | 1); ((Component)val3).gameObject.SetActive(true); val3.Pause(false); UpdateAnchors(anchors, normals); } } private static float Unit(int number) { int num = (number ^ (number >>> 16)) * 2146121005; int num2 = (num ^ (num >>> 15)) * -2073254261; return (float)(uint)((num2 ^ (num2 >>> 16)) & 0xFFFFFF) / 16777216f; } private void Emit(float seconds) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) if (anchors.Length == 0) { return; } float num = Mathf.Clamp01(FateVisuals.Intensity); Quaternion val = Quaternion.Inverse(((Component)this).transform.rotation); Matrix4x4 val2 = Matrix4x4.Rotate(val) * ((Component)this).transform.localToWorldMatrix; Matrix4x4 worldToLocalMatrix = ((Component)this).transform.worldToLocalMatrix; Matrix4x4 transpose = ((Matrix4x4)(ref worldToLocalMatrix)).transpose; foreach (Channel channel in channels) { if (!Object.op_Implicit((Object)(object)channel.System) || !Object.op_Implicit((Object)(object)channel.Renderer)) { continue; } channel.Fraction += seconds * channel.Rate * Mathf.Lerp(0.35f, 1f, num) * PulsePower; int num2 = Math.Min(channel.Capacity, Mathf.FloorToInt(channel.Fraction)); channel.Fraction -= num2; for (int i = 0; i < num2; i++) { int num3 = (seed + channel.Serial++ * 199 + channel.Element * 9281 + (channel.Accent ? 31 : 0)) | 1; float num4 = Unit(num3); float num5 = Unit(num3 + 71); float num6 = Unit(num3 + 127); int num7 = Math.Min(anchors.Length - 1, (int)(num4 * (float)anchors.Length)); Vector3 val3; if (normals.Length != anchors.Length) { val3 = Vector3.up; } else { Vector3 val4 = ((Matrix4x4)(ref transpose)).MultiplyVector(normals[num7]); val3 = ((Vector3)(ref val4)).normalized; } Vector3 val5 = val3; Vector3 val6 = ((channel.Element == 0) ? (Vector3.up * (channel.Accent ? 0.35f : 0.21f)) : ((channel.Element == 1) ? (Vector3.down * (channel.Accent ? 0.07f : 0.016f)) : (Vector3.up * 0.018f))); val6 += val5 * (channel.Accent ? 0.06f : 0.015f); Color val7 = style.ElementColor(channel.Element) * ((channel.Element == 2) ? 1.9f : 1.5f); float num8 = ((channel.Element == 0) ? 0.74f : ((channel.Element == 2) ? 0.82f : (channel.Accent ? 0.85f : ((channel.Element >= 5) ? 0.7f : 0.56f)))); val7.a = Mathf.Clamp01(num8 * (0.4f + 0.85f * channel.Strength) * num * PulsePower); EmitParams val8 = default(EmitParams); ((EmitParams)(ref val8)).position = (channel.World ? ((Component)this).transform.TransformPoint(anchors[num7]) : ((Matrix4x4)(ref val2)).MultiplyVector(anchors[num7])); ((EmitParams)(ref val8)).velocity = (channel.World ? (val6 * (0.6f + 0.4f * channel.Strength)) : (val * (val6 * (0.6f + 0.4f * channel.Strength)))); ((EmitParams)(ref val8)).startSize = channel.Size * (0.78f + 0.44f * num6); ((EmitParams)(ref val8)).startLifetime = channel.Lifetime * (0.8f + 0.4f * num5); ((EmitParams)(ref val8)).startColor = Color32.op_Implicit(val7); ((EmitParams)(ref val8)).rotation = ((channel.Element == 0) ? ((num6 - 0.5f) * 20f) : (num6 * 360f)); ((EmitParams)(ref val8)).randomSeed = (uint)(num3 | 1); ((EmitParams)(ref val8)).applyShapeToPosition = false; EmitParams val9 = val8; channel.System.Emit(val9, 1); } } } private void WarmUp(float duration) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //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_0026: Unknown result type (might be due to invalid IL or missing references) simulationStretch = Matrix4x4.Rotate(Quaternion.Inverse(((Component)this).transform.rotation)) * ((Component)this).transform.localToWorldMatrix; Array.Copy(anchors, previousAnchors, anchors.Length); foreach (Channel channel in channels) { channel.System.Clear(false); channel.Serial = 0; channel.Fraction = Unit(setKey + channel.Element) * 0.8f; } int num = Mathf.Clamp(Mathf.CeilToInt(duration / 0.05f), 1, 60); for (int i = 0; i < num; i++) { Emit(0.05f); foreach (Channel channel2 in channels) { channel2.System.Simulate(0.05f, false, false, false); } } foreach (Channel channel3 in channels) { channel3.System.Pause(false); } } public void AnimatePreview(float time) { if (!HasVisual) { return; } WarmUp(Mathf.Clamp(time, 0.2f, 3f)); foreach (Channel channel in channels) { channel.System.Pause(false); } } public void RefreshPlacement() { if (configured) { WarmUp(0.75f); SetVisible(visible); } } public void SetVisible(bool value) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) bool flag = value && !visible; visible = value; foreach (Channel channel in channels) { if (Object.op_Implicit((Object)(object)channel.Renderer)) { bool flag2 = value && ((Behaviour)this).isActiveAndEnabled && FateVisuals.Intensity > 0f && FateVisuals.Particles; ((Renderer)channel.Renderer).enabled = flag2; if (!flag2) { channel.System.Clear(false); channel.System.Pause(false); } } } if (flag && configured) { lastPosition = ((Component)this).transform.position; WarmUp(0.15f); } } public void Pulse() { if (configured && !reduced) { pulseUntil = Time.unscaledTime + 0.65f; } } private void RefreshSimulationStretch() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) Matrix4x4 val = Matrix4x4.Rotate(Quaternion.Inverse(((Component)this).transform.rotation)) * ((Component)this).transform.localToWorldMatrix; bool flag = false; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { flag |= Mathf.Abs(((Matrix4x4)(ref val))[i, j] - ((Matrix4x4)(ref simulationStretch))[i, j]) > 0.0001f * Mathf.Max(1f, Mathf.Abs(((Matrix4x4)(ref val))[i, j])); } } if (!flag) { return; } bool flag2 = Mathf.Abs(((Matrix4x4)(ref simulationStretch)).determinant) > 1E-12f; Matrix4x4 val2 = (flag2 ? (val * ((Matrix4x4)(ref simulationStretch)).inverse) : Matrix4x4.identity); foreach (Channel channel in channels) { if (!Object.op_Implicit((Object)(object)channel.System) || channel.World) { continue; } if (!flag2) { channel.System.Clear(false); continue; } int particles = channel.System.GetParticles(channel.Particles); for (int k = 0; k < particles; k++) { ((Particle)(ref channel.Particles[k])).position = ((Matrix4x4)(ref val2)).MultiplyVector(((Particle)(ref channel.Particles[k])).position); } channel.System.SetParticles(channel.Particles, particles); } simulationStretch = val; } private void FollowDeformation() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: 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) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) if (anchors.Length == 0 || anchors.Length != previousAnchors.Length) { return; } bool flag = false; for (int i = 0; i < anchors.Length; i++) { if (flag) { break; } Vector3 val = anchors[i] - previousAnchors[i]; flag = ((Vector3)(ref val)).sqrMagnitude > 1E-10f; } if (!flag) { return; } foreach (Channel channel in channels) { if (!channel.World && Object.op_Implicit((Object)(object)channel.System)) { int particles = channel.System.GetParticles(channel.Particles); for (int j = 0; j < particles; j++) { int num = Math.Min(anchors.Length - 1, (int)(Unit((int)((Particle)(ref channel.Particles[j])).randomSeed) * (float)anchors.Length)); ref Particle reference = ref channel.Particles[j]; ((Particle)(ref reference)).position = ((Particle)(ref reference)).position + ((Matrix4x4)(ref simulationStretch)).MultiplyVector(anchors[num] - previousAnchors[num]); } channel.System.SetParticles(channel.Particles, particles); } } Array.Copy(anchors, previousAnchors, anchors.Length); } private void LateUpdate() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (!configured || !visible) { return; } if (FateVisuals.Intensity <= 0f || !FateVisuals.Particles) { SetVisible(value: false); return; } Vector3 val = ((Component)this).transform.position - lastPosition; if (((Vector3)(ref val)).sqrMagnitude > 6.25f) { foreach (Channel channel in channels) { if (Object.op_Implicit((Object)(object)channel.System)) { channel.System.Clear(false); } } } lastPosition = ((Component)this).transform.position; RefreshSimulationStretch(); FollowDeformation(); foreach (Channel channel2 in channels) { if (Object.op_Implicit((Object)(object)channel2.System)) { channel2.System.Simulate(Mathf.Max(0f, Time.deltaTime), false, false, false); } } Emit(Mathf.Min(0.1f, Time.deltaTime)); foreach (Channel channel3 in channels) { if (Object.op_Implicit((Object)(object)channel3.System)) { channel3.System.Pause(false); } } } public void ClearVisual() { configured = false; foreach (Channel channel in channels) { if (Object.op_Implicit((Object)(object)channel.System)) { ((Renderer)channel.Renderer).enabled = false; channel.System.Stop(false, (ParticleSystemStopBehavior)0); ((Component)channel.System).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)channel.System).gameObject); } } channels.Clear(); systems.Clear(); Live.Remove(this); } private void OnDisable() { foreach (Channel channel in channels) { if (Object.op_Implicit((Object)(object)channel.Renderer)) { ((Renderer)channel.Renderer).enabled = false; channel.System.Clear(false); channel.System.Pause(false); } } } private void OnEnable() { if (configured) { SetVisible(visible); } } private void OnDestroy() { ClearVisual(); } public static void Shutdown() { ElementalVfx[] array = Live.ToArray(); foreach (ElementalVfx elementalVfx in array) { if (Object.op_Implicit((Object)(object)elementalVfx)) { elementalVfx.ClearVisual(); } } Live.Clear(); AuthoredVfxAssets.Shutdown(); NativeEquipmentVfxAssets.Shutdown(); EquipmentVfxGeometry.ClearCache(); } } internal sealed class ElementalVfxMarker : MonoBehaviour { } public sealed class EquipmentSurfaceVfx : MonoBehaviour { private Renderer source; private Renderer surface; private Mesh? ownedMesh; private MaterialPropertyBlock[] blocks = Array.Empty<MaterialPropertyBlock>(); private bool visible = true; private bool released; private float pulseUntil; private float nextBoneSync; private float lastPower = -1f; private float lastPulse = -1f; public Renderer Source => source; public Renderer Surface => surface; public bool OwnsMesh => Object.op_Implicit((Object)(object)ownedMesh); public bool HasVisual { get { if (!released && Object.op_Implicit((Object)(object)source)) { return Object.op_Implicit((Object)(object)surface); } return false; } } public static bool AcceptsEmission(Renderer source, Transform equipment, Vector3 point, bool armor = false) { //IL_001a: 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) Mesh val = EquipmentVfxGeometry.MeshOf(source); if (Object.op_Implicit((Object)(object)val)) { return EquipmentVfxProfiles.Resolve(source, equipment, armor).Accepts(point, val.bounds); } return false; } public static EquipmentSurfaceVfx? Create(Renderer source, Transform equipment, FateVisualStyle style, int seed, bool armor, IReadOnlyCollection<Transform>? region = null) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Unknown result type (might be due to invalid IL or missing references) //IL_03fa: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) if (!EquipmentVfxGeometry.IsEquipmentRenderer(source)) { return null; } Mesh val = EquipmentVfxGeometry.MeshOf(source); Material val2 = AuthoredVfxAssets.CreateSurfaceMaterial(style, seed, armor); if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2)) { return null; } Mesh val3 = ((region == null) ? null : EquipmentVfxGeometry.CreateRegionMesh(source, region)); if (region != null && !Object.op_Implicit((Object)(object)val3)) { return null; } GameObject val4 = new GameObject("Nornforged authored equipment surface"); val4.transform.SetParent(((Component)source).transform, false); val4.layer = ((Component)source).gameObject.layer; val4.AddComponent<ElementalVfxMarker>(); EquipmentSurfaceVfx equipmentSurfaceVfx = val4.AddComponent<EquipmentSurfaceVfx>(); equipmentSurfaceVfx.source = source; equipmentSurfaceVfx.ownedMesh = val3; Mesh val5 = (Object.op_Implicit((Object)(object)val3) ? val3 : val); float num = 0f; SkinnedMeshRenderer val6 = (SkinnedMeshRenderer)(object)((source is SkinnedMeshRenderer) ? source : null); if (val6 != null) { if (val5.isReadable) { if (!Object.op_Implicit((Object)(object)val3)) { val5 = (equipmentSurfaceVfx.ownedMesh = Object.Instantiate<Mesh>(val5)); ((Object)val5).name = "Nornforged stable skin surface"; } val5.SetUVs(3, new List<Vector3>(val5.vertices)); num = 1f; } else { num = 2f; } SkinnedMeshRenderer val7 = val4.AddComponent<SkinnedMeshRenderer>(); val7.sharedMesh = val5; val7.bones = val6.bones; val7.rootBone = val6.rootBone; val7.quality = val6.quality; ((Renderer)val7).localBounds = ((Renderer)val6).localBounds; val7.updateWhenOffscreen = val6.updateWhenOffscreen; equipmentSurfaceVfx.surface = (Renderer)(object)val7; } else { val4.AddComponent<MeshFilter>().sharedMesh = val5; equipmentSurfaceVfx.surface = (Renderer)(object)val4.AddComponent<MeshRenderer>(); } Renderer obj = equipmentSurfaceVfx.surface; obj.shadowCastingMode = (ShadowCastingMode)0; obj.receiveShadows = false; obj.lightProbeUsage = (LightProbeUsage)0; obj.reflectionProbeUsage = (ReflectionProbeUsage)0; obj.motionVectorGenerationMode = (MotionVectorGenerationMode)2; obj.allowOcclusionWhenDynamic = source.allowOcclusionWhenDynamic; obj.sharedMaterials = Enumerable.Repeat<Material>(val2, Math.Max(1, val5.subMeshCount)).ToArray(); equipmentSurfaceVfx.blocks = (MaterialPropertyBlock[])(object)new MaterialPropertyBlock[Math.Max(1, val5.subMeshCount)]; Bounds bounds = val.bounds; EquipmentVfxProfile equipmentVfxProfile = EquipmentVfxProfiles.Resolve(source, equipment, armor); Material[] sharedMaterials = source.sharedMaterials; for (int i = 0; i < equipmentSurfaceVfx.blocks.Length; i++) { MaterialPropertyBlock val8 = new MaterialPropertyBlock(); equipmentSurfaceVfx.blocks[i] = val8; AuthoredVfxAssets.ApplySurfaceStyle(val8, style, seed, armor); val8.SetFloat("_Reduced", (float)(FateVisuals.Reduced ? 1 : 0)); val8.SetFloat("_RestCoordinates", num); val8.SetFloat("_MaskMode", (float)(Object.op_Implicit((Object)(object)val3) ? 1 : 0)); val8.SetVector("_Axis", Vector4.op_Implicit(equipmentVfxProfile.Axis)); val8.SetVector("_Region", equipmentVfxProfile.Region); val8.SetVector("_GripRegion", equipmentVfxProfile.GripRegion); val8.SetVector("_BoundsMin", Vector4.op_Implicit(((Bounds)(ref bounds)).min)); val8.SetVector("_BoundsSize", Vector4.op_Implicit(Vector3.Max(((Bounds)(ref bounds)).size, Vector3.one * 0.0001f))); val8.SetVector("_PatternScale", Vector4.op_Implicit(((Component)source).transform.lossyScale)); Material val9 = ((i < sharedMaterials.Length) ? sharedMaterials[i] : null); bool flag = !Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val9) && (val9.IsKeywordEnabled("_ALPHATEST_ON") || val9.GetTag("RenderType", false, "") == "TransparentCutout"); val8.SetFloat("_UseCutout", (float)(flag ? 1 : 0)); val8.SetFloat("_Cutoff", (flag && val9.HasProperty("_Cutoff")) ? val9.GetFloat("_Cutoff") : 0.5f); if (flag && Object.op_Implicit((Object)(object)val9.mainTexture)) { val8.SetTexture("_MainTex", val9.mainTexture); Vector2 mainTextureScale = val9.mainTextureScale; Vector2 mainTextureOffset = val9.mainTextureOffset; val8.SetVector("_MainTex_ST", new Vector4(mainTextureScale.x, mainTextureScale.y, mainTextureOffset.x, mainTextureOffset.y)); } if (Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val9) && region != null) { string text = (region.Any((Transform bone) => ((Object)bone).name.IndexOf("Leg", StringComparison.OrdinalIgnoreCase) >= 0) ? "_LegsTex" : "_ChestTex"); Texture val10 = (val9.HasProperty(text) ? val9.GetTexture(text) : null); if (Object.op_Implicit((Object)(object)val10)) { val8.SetFloat("_UseCutout", 1f); val8.SetFloat("_Cutoff", 0.08f); val8.SetTexture("_MainTex", val10); val8.SetVector("_MainTex_ST", new Vector4(1f, 1f, 0f, 0f)); } } val8.SetFloat("_PartEnabled", (float)((Object.op_Implicit((Object)(object)val3) || sharedMaterials.Length == 0 || EquipmentVfxGeometry.IsEquipmentMaterial(val9)) ? 1 : 0)); } equipmentSurfaceVfx.Sync(); return equipmentSurfaceVfx; } public void SetVisible(bool value) { if (visible != value) { visible = value; Sync(); } } public void Pulse() { if (!FateVisuals.Reduced) { pulseUntil = Time.unscaledTime + 0.65f; } } public void Sync() { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) if (released || !Object.op_Implicit((Object)(object)source) || !Object.op_Implicit((Object)(object)surface)) { return; } ((Component)surface).gameObject.layer = ((Component)source).gameObject.layer; surface.enabled = visible && ((Behaviour)this).isActiveAndEnabled && source.enabled && !source.forceRenderingOff && ((Component)source).gameObject.activeInHierarchy && FateVisuals.Intensity > 0f && FateVisuals.Particles; Renderer obj = surface; SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((obj is SkinnedMeshRenderer) ? obj : null); if (val != null) { Renderer obj2 = source; SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((obj2 is SkinnedMeshRenderer) ? obj2 : null); if (val2 != null) { if (Time.unscaledTime >= nextBoneSync) { if ((Object)(object)val.rootBone != (Object)(object)val2.rootBone || !val.bones.SequenceEqual(val2.bones)) { val.bones = val2.bones; val.rootBone = val2.rootBone; } nextBoneSync = Time.unscaledTime + 0.5f; } ((Renderer)val).localBounds = ((Renderer)val2).localBounds; val.quality = val2.quality; val.updateWhenOffscreen = val2.updateWhenOffscreen; int num = Math.Min(Object.op_Implicit((Object)(object)val.sharedMesh) ? val.sharedMesh.blendShapeCount : 0, Object.op_Implicit((Object)(object)val2.sharedMesh) ? val2.sharedMesh.blendShapeCount : 0); for (int i = 0; i < num; i++) { val.SetBlendShapeWeight(i, val2.GetBlendShapeWeight(i)); } } } float num2 = 1f + 0.24f * Mathf.SmoothStep(0f, 1f, Mathf.Clamp01((pulseUntil - Time.unscaledTime) / 0.65f)); float num3 = Mathf.Clamp01(FateVisuals.Intensity) * (FateVisuals.Reduced ? 0.55f : 1f); if (!Mathf.Approximately(num3, lastPower) || !Mathf.Approximately(num2, lastPulse)) { lastPower = num3; lastPulse = num2; for (int j = 0; j < blocks.Length; j++) { blocks[j].SetFloat("_Power", num3); blocks[j].SetFloat("_Pulse", num2); surface.SetPropertyBlock(blocks[j], j); } } } private void LateUpdate() { Sync(); } private void OnDisable() { if (Object.op_Implicit((Object)(object)surface)) { surface.enabled = false; } } private void OnEnable() { if (Object.op_Implicit((Object)(object)source) && Object.op_Implicit((Object)(object)surface)) { Sync(); } } public void Release() { if (!released) { released = true; if (Object.op_Implicit((Object)(object)surface)) { surface.enabled = false; } if (Object.op_Implicit((Object)(object)ownedMesh)) { Object.Destroy((Object)(object)ownedMesh); ownedMesh = null; } ((Component)this).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)this).gameObject); } } private void OnDestroy() { if (Object.op_Implicit((Object)(object)ownedMesh)) { Object.Destroy((Object)(object)ownedMesh); } ownedMesh = null; } } public static class EquipmentVfxGeometry { private sealed class AlphaPlane { internal static readonly AlphaPlane Missing = new AlphaPlane(); internal byte[] Values = Array.Empty<byte>(); internal int Width; internal int Height; internal TextureWrapMode WrapU; internal TextureWrapMode WrapV; private static int Wrap(int value, int size, TextureWrapMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 if ((int)mode == 1) { return Mathf.Clamp(value, 0, size - 1); } if ((int)mode == 3) { return Mathf.Clamp((value < 0) ? (-value - 1) : value, 0, size - 1); } if ((int)mode == 2) { int num = (value % (size * 2) + size * 2) % (size * 2); if (num >= size) { return size * 2 - num - 1; } return num; } return (value % size + size) % size; } internal bool Allows(Vector2 uv, float cutoff) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) if (Values.Length == 0 || !Finite(uv.x) || !Finite(uv.y)) { return false; } float num = uv.x * (float)Width - 0.5f; float num2 = uv.y * (float)Height - 0.5f; int num3 = Mathf.FloorToInt(num); int num4 = Mathf.FloorToInt(num2); int num5 = 255; for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { num5 = Math.Min(num5, Values[Wrap(num4 + i, Height, WrapV) * Width + Wrap(num3 + j, Width, WrapU)]); } } return (float)num5 / 255f >= cutoff; } } private sealed class MaterialMask { internal bool Eligible; internal AlphaPlane? Alpha; internal Vector2 Scale = Vector2.one; internal Vector2 Offset; internal float Cutoff; internal string Key = ""; internal bool Allows(Vector2 uv) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (Eligible) { if (Alpha != null) { return Alpha.Allows(Vector2.Scale(uv, Scale) + Offset, Cutoff); } return true; } return false; } } private sealed class MeshLayouts { private readonly Vector3[] vertices; private readonly Vector3[] normals; private readonly Vector2[] uv; private readonly int[] triangles; private readonly int[] triangleMaterials; private readonly Bounds bounds; private readonly Matrix4x4[] bindposes; private readonly Influence[][] influences; private readonly Dictionary<string, Layout> layouts = new Dictionary<string, Layout>(); internal MeshLayouts(Mesh mesh) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) bounds = mesh.bounds; vertices = (normals = Array.Empty<Vector3>()); triangles = (triangleMaterials = Array.Empty<int>()); uv = Array.Empty<Vector2>(); bindposes = Array.Empty<Matrix4x4>(); influences = Array.Empty<Influence[]>(); try { Vector3[] array3; Vector3[] array4; int[] array5; Influence[][] array6; Vector2[] array7; int[] array8; if (mesh.isReadable) { vertices = mesh.vertices; uv = mesh.uv; Vector3[] array = mesh.normals; normals = (Vector3[])((array.Length == vertices.Length) ? ((Array)array) : ((Array)new Vector3[vertices.Length])); List<int> list = new List<int>(); List<int> list2 = new List<int>(); for (int i = 0; i < mesh.subMeshCount; i++) { if ((int)mesh.GetTopology(i) == 0) { int[] array2 = mesh.GetTriangles(i); list.AddRange(array2); list2.AddRange(Enumerable.Repeat(i, array2.Length / 3)); } } triangles = list.ToArray(); triangleMaterials = list2.ToArray(); } else if (ReadGpuMesh(mesh, out array3, out array4, out array5, out array6, out array7, out array8)) { vertices = array3; normals = array4; triangles = array5; influences = array6; uv = array7; triangleMaterials = array8; } } catch (Exception) { vertices = (normals = Array.Empty<Vector3>()); triangles = Array.Empty<int>(); } try { bindposes = mesh.bindposes; } catch (Exception) { } try { Influence[][] source = ReadInfluences(mesh, vertices.Length); if (source.Any((Influence[] vertex) => vertex.Length != 0)) { influences = source; } } catch (Exception) { } } internal Layout Get(Mesh mesh, int count, int[]? bones, MaterialMask[] masks, EquipmentVfxProfile? profile) { //IL_0164: Unknown result type (might be due to invalid IL or missing references) string key = count.ToString(CultureInfo.InvariantCulture) + ((bones == null) ? "" : (":" + string.Join(",", bones))) + "/" + string.Join(";", masks.Select((MaterialMask mask) => mask.Key)) + "/" + profile?.CacheKey; if (layouts.TryGetValue(key, out Layout value)) { return value; } bool[] eligible = null; if (bones != null) { HashSet<int> selected = new HashSet<int>(bones); eligible = (from index in Enumerable.Range(0, vertices.Length) select index < influences.Length && influences[index].Where((Influence influence) => selected.Contains(influence.Bone)).Sum((Influence influence) => influence.Weight) >= 0.45f).ToArray(); } bool flag = bones != null || masks.Any((MaterialMask mask) => !mask.Eligible || mask.Alpha != null) || (profile?.Restricted ?? false); Layout layout = Build(vertices, normals, triangles, bounds, count, eligible, uv, triangleMaterials, masks, flag, profile); if (layout == Layout.Empty) { if (layouts.Count >= 64) { layouts.Clear(); } layouts.Add(key, layout); return layout; } bool meshVertices = layout.Surface; if (!layout.Surface && bones == null && !flag) { layout = NativeSurface(mesh, count) ?? layout; } layout.HasMeshVertexIds = meshVertices; layout.Bindposes = (meshVertices ? bindposes : Array.Empty<Matrix4x4>()); layout.Influences = layout.VertexIds.Select((int index) => (!meshVertices || index >= influences.Length) ? Array.Empty<Influence>() : influences[index]).ToArray(); if (meshVertices && mesh.isReadable) { try { layout.Blends = ReadBlends(mesh, layout.VertexIds); } catch (Exception) { layout.Blends = Array.Empty<Blend>(); } } if (layouts.Count >= 64) { layouts.Clear(); } layouts.Add(key, layout); return layout; } internal Mesh? Region(Mesh original, Transform[] bones, HashSet<Transform> selected, MaterialMask[] masks) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Expected O, but got Unknown if (vertices.Length == 0 || triangles.Length == 0 || influences.Length != vertices.Length) { return null; } Color[] array = (Color[])(object)new Color[vertices.Length]; for (int i = 0; i < array.Length; i++) { float num = influences[i].Where((Influence value) => value.Bone >= 0 && value.Bone < bones.Length && selected.Contains(bones[value.Bone])).Sum((Influence value) => value.Weight); array[i] = new Color(1f, 1f, 1f, Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.4f, 0.75f, num))); } List<int> list = new List<int>(); for (int num2 = 0; num2 + 2 < triangles.Length; num2 += 3) { if ((num2 / 3 >= triangleMaterials.Length || triangleMaterials[num2 / 3] >= masks.Length || masks[triangleMaterials[num2 / 3]].Eligible) && array[triangles[num2]].a > 0f && array[triangles[num2 + 1]].a > 0f && array[triangles[num2 + 2]].a > 0f) { list.Add(triangles[num2]); list.Add(triangles[num2 + 1]); list.Add(triangles[num2 + 2]); } } if (list.Count == 0) { return null; } Mesh val; if (original.isReadable) { val = Object.Instantiate<Mesh>(original); val.subMeshCount = 1; val.triangles = list.ToArray(); } else { if (original.blendShapeCount > 0 || bindposes.Length == 0) { return null; } val = new Mesh { indexFormat = original.indexFormat, vertices = vertices, normals = normals, uv = uv, triangles = list.ToArray(), bindposes = bindposes }; BoneWeight[] array2 = (BoneWeight[])(object)new BoneWeight[vertices.Length]; for (int num3 = 0; num3 < array2.Length; num3++) { Influence[] array3 = influences[num3].OrderByDescending((Influence value) => value.Weight).Take(4).ToArray(); float num4 = array3.Sum((Influence value) => value.Weight); for (int num5 = 0; num5 < array3.Length; num5++) { Influence influence = array3[num5]; float num6 = ((num4 > 0f) ? (influence.Weight / num4) : 0f); switch (num5) { case 0: ((BoneWeight)(ref array2[num3])).boneIndex0 = influence.Bone; ((BoneWeight)(ref array2[num3])).weight0 = num6; break; case 1: ((BoneWeight)(ref array2[num3])).boneIndex1 = influence.Bone; ((BoneWeight)(ref array2[num3])).weight1 = num6; break; case 2: ((BoneWeight)(ref array2[num3])).boneIndex2 = influence.Bone; ((BoneWeight)(ref array2[num3])).weight2 = num6; break; default: ((BoneWeight)(ref array2[num3])).boneIndex3 = influence.Bone; ((BoneWeight)(ref array2[num3])).weight3 = num6; break; } } } val.boneWeights = array2; } ((Object)val).name = "Nornforged isolated worn surface region"; val.colors = array; val.bounds = original.bounds; return val; } } internal struct Influence { internal int Bone; internal float Weight; } internal sealed class BlendFrame { internal float Weight; internal Vector3[] Positions = Array.Empty<Vector3>(); internal Vector3[] Normals = Array.Empty<Vector3>(); } internal sealed class Blend { internal int Index; internal BlendFrame[] Frames = Array.Empty<BlendFrame>(); } internal sealed class Layout { internal static readonly Layout Empty = new Layout(); internal bool Surface; internal bool HasMeshVertexIds; internal Vector3[] Vertices = Array.Empty<Vector3>(); internal Vector3[] Normals = Array.Empty<Vector3>(); internal Vector3[] Weights = Array.Empty<Vector3>(); internal int[] VertexIds = Array.Empty<int>(); internal int[] Indices = Array.Empty<int>(); internal Matrix4x4[] Bindposes = Array.Empty<Matrix4x4>(); internal Influence[][] Influences = Array.Empty<Influence[]>(); internal Blend[] Blends = Array.Empty<Blend>(); } public sealed class SurfaceSamples : IDisposable { private readonly Layout layout; private readonly SkinnedMeshRenderer? skin; private readonly Transform[] bones; private readonly Matrix4x4[] matrices; private readonly Matrix4x4[] normalMatrices; private readonly bool[] boneValid; private readonly int[] usedBones; private readonly Vector3[] positions; private readonly Vector3[] normals; private readonly List<Vector3> bakedPositions = new List<Vector3>(); private readonly List<Vector3> bakedNormals = new List<Vector3>(); private bool disposed; public Renderer Source { get; } public Mesh? Mesh { get; } public Vector3[] Points { get; private set; } public Vector3[] Normals { get; private set; } public bool UsesSurface => layout.Surface; public bool CanRefreshBaked => layout.HasMeshVertexIds; public bool UsesSkinning { get { if (Object.op_Implicit((Object)(object)skin)) { return usedBones.Length != 0; } return false; } } public bool UsesBlendShapes { get { if (Object.op_Implicit((Object)(object)skin)) { return layout.Blends.Length != 0; } return false; } } public bool HasRestrictedMask { get; } public bool NativeVisible { get { if (Object.op_Implicit((Object)(object)Source) && Source.enabled && ((Component)Source).gameObject.activeInHierarchy) { return !Source.forceRenderingOff; } return false; } } public int SampledVertexCount => layout.Vertices.Length; internal SurfaceSamples(Renderer source, Mesh? mesh, Layout layout, bool restricted = false) { SurfaceSamples surfaceSamples = this; Source = source; Mesh = mesh; this.layout = layout; skin = (SkinnedMeshRenderer?)(object)((source is SkinnedMeshRenderer) ? source : null); HasRestrictedMask = restricted; bones = (Object.op_Implicit((Object)(object)skin) ? skin.bones : Array.Empty<Transform>()); usedBones = (from value in layout.Influences.SelectMany((Influence[] value) => value) select value.Bone into index where index >= 0 && index < surfaceSamples.bones.Length && index < layout.Bindposes.Length select index).Distinct().ToArray(); matrices = (Matrix4x4[])(object)new Matrix4x4[bones.Length]; normalMatrices = (Matrix4x4[])(object)new Matrix4x4[bones.Length]; boneValid = new bool[bones.Length]; positions = (Vector3[])(object)new Vector3[layout.Vertices.Length]; normals = (Vector3[])(object)new Vector3[layout.Vertices.Length]; Points = (Vector3[])(object)new Vector3[layout.Weights.Length]; Normals = (Vector3[])(object)new Vector3[Points.Length]; Refresh(); } public bool RefreshBaked(Mesh baked, Transform? targetSpace = null) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (disposed || !Object.op_Implicit((Object)(object)Source) || !Object.op_Implicit((Object)(object)Mesh) || (Object)(object)MeshOf(Source) != (Object)(object)Mesh || !layout.HasMeshVertexIds || !Object.op_Implicit((Object)(object)baked)) { return false; } baked.GetVertices(bakedPositions); baked.GetNormals(bakedNormals); for (int i = 0; i < layout.VertexIds.Length; i++) { int num = layout.VertexIds[i]; if (num < 0 || num >= bakedPositions.Count) { return false; } positions[i] = bakedPositions[num]; normals[i] = ((num < bakedNormals.Count) ? bakedNormals[num] : layout.Normals[i]); } UpdatePoints(targetSpace); return Points.Length != 0; } public bool Refresh(Transform? targetSpace = null) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected I4, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due