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 Fysik v0.1.1
plugins/Fysik.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Fysik.Game; using Fysik.Structure; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("menezesbruno")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Bruno Menezes")] [assembly: AssemblyDescription("Structural physics for building. Shape matters: arches carry load in compression, triangulated trusses stay rigid, cantilevers bend. Replaces vanilla support with a frame solver; overloaded pieces crack before they fall. Work in progress.")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+d5f597f3f3f50f6c0d6fb08d6e7ee840c25f7654")] [assembly: AssemblyProduct("Fysik")] [assembly: AssemblyTitle("Fysik")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/menezesbruno/Fysik")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [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 Fysik { internal enum StructureMode { Physics, DisplayOnly, Sandbox } internal enum WorldBuildingMode { Vanilla, Physics } internal enum Difficulty { Relaxed, Normal, Strict } internal static class FysikConfig { internal sealed class MaterialEntries { public string Name; public ConfigEntry<float> Density; public ConfigEntry<float> Compression; public ConfigEntry<float> Tension; public ConfigEntry<float> Shear; public ConfigEntry<float> Slenderness; } internal const float XRayRadiusMin = 40f; internal const float XRayRadiusMax = 80f; internal const float XRayRadiusStep = 10f; internal static readonly Dictionary<MaterialType, MaterialEntries> Materials = new Dictionary<MaterialType, MaterialEntries>(); internal static ConfigEntry<StructureMode> Mode { get; private set; } internal static ConfigEntry<WorldBuildingMode> WorldBuildings { get; private set; } internal static ConfigEntry<float> CrackWarningSeconds { get; private set; } internal static ConfigEntry<bool> DamageWeakens { get; private set; } internal static ConfigEntry<int> MaxFrameBodies { get; private set; } internal static ConfigEntry<Difficulty> StructureDifficulty { get; private set; } internal static double StrengthMultiplier { get { if (StructureDifficulty.Value != Difficulty.Relaxed) { if (StructureDifficulty.Value != Difficulty.Strict) { return 1.0; } return 0.7; } return 1.5; } } internal static ConfigEntry<float> FrameBudgetMs { get; private set; } internal static ConfigEntry<bool> BackgroundSolver { get; private set; } internal static ConfigEntry<bool> HammerInfo { get; private set; } internal static ConfigEntry<bool> PlacementPreview { get; private set; } internal static ConfigEntry<bool> CollapseAnimation { get; private set; } internal static ConfigEntry<KeyboardShortcut> XRayKey { get; private set; } internal static ConfigEntry<float> XRayRadius { get; private set; } internal static ConfigEntry<KeyboardShortcut> XRayRadiusKey { get; private set; } internal static ConfigEntry<bool> LogSolves { get; private set; } internal static ConfigEntry<bool> LogGeometry { get; private set; } internal static ConfigEntry<KeyboardShortcut> DumpKey { get; private set; } internal static MaterialEntries Unknown { get; private set; } internal static void Bind(ConfigFile config) { //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Expected O, but got Unknown //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) Mode = config.Bind<StructureMode>("Structure", "Mode", StructureMode.Physics, Server("Physics: overloaded pieces crack, then fall (vanilla support is replaced). DisplayOnly: vanilla decides what falls, Fysik only shows the forces. Sandbox: nothing falls for lack of support, Fysik shows the forces (calibration, test worlds).", null)); WorldBuildings = config.Bind<WorldBuildingMode>("Structure", "WorldBuildings", WorldBuildingMode.Vanilla, Server("Buildings that come with the world, not built by a player: stone towers, abandoned houses, villages, ruins, fortresses. Vanilla: they keep vanilla support and Fysik never brings them down; only what players build is simulated, and a player build resting on them is held as if on rock. Physics: they are simulated like player builds. Warning: many were not built to stand up to real forces and may crack and fall as soon as a player comes near, changing the world for good.", null)); CrackWarningSeconds = config.Bind<float>("Failure", "CrackWarningSeconds", 5f, Server("Seconds an overloaded piece stays cracked (red, shaking) before it falls, giving time to shore it up. Once the first piece falls, the rest of the collapse is instant.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f))); DamageWeakens = config.Bind<bool>("Failure", "DamageWeakens", true, Server("Badly damaged pieces are weaker: down to half health they keep their full strength (rain wear alone never weakens them); below that, strength falls to 25% at the brink of breaking, so a pillar under attack can crack under the same load. Off: damage never changes strength.", null)); MaxFrameBodies = config.Bind<int>("Structure", "MaxFrameBodies", 2000, Server("Largest connected structure (pieces) solved with the full frame analysis. Bigger ones use a simplified load-path model, which does not recognise arches. Higher values recognise arches in big bases but take longer to update (about 0.5 s for 2000 pieces, 2 s for 5000, on a worker thread).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(500, 5000))); StructureDifficulty = config.Bind<Difficulty>("Structure", "Difficulty", Difficulty.Normal, Server("Relaxed: materials 50% stronger (longer spans and cantilevers). Normal. Strict: materials 30% weaker (structures need more thought).", null)); FrameBudgetMs = config.Bind<float>("Performance", "FrameBudgetMs", 1.5f, new ConfigDescription("Milliseconds per frame spent on structural calculations; the rest waits for the next frame.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 10f), Array.Empty<object>())); BackgroundSolver = config.Bind<bool>("Performance", "BackgroundSolver", true, "Solve structures on a worker thread, so big bases update in about a second without taking frame time. Off: the solver shares the per-frame budget (slower for big structures)."); HammerInfo = config.Bind<bool>("Display", "HammerInfo", true, "Color the piece under the hammer by Fysik stress (blue = relaxed, red = at its limit) instead of vanilla support, and show its value next to the crosshair."); PlacementPreview = config.Bind<bool>("Display", "PlacementPreview", true, "While placing a piece, color it by the stress it would have once placed, and warn if it would overload another piece."); CollapseAnimation = config.Bind<bool>("Display", "CollapseAnimation", true, "Pieces brought down by Fysik fall together as debris and burst when they hit something, like a building being imploded. Off: they shatter where they stand, as in vanilla."); XRayKey = config.Bind<KeyboardShortcut>("Display", "XRayKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Toggles the X-ray view: every piece nearby is colored by stress."); XRayRadius = config.Bind<float>("Display", "XRayRadius", 40f, new ConfigDescription("X-ray view radius, metres. Also changed in game with XRayRadiusKey.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(40f, 80f), Array.Empty<object>())); XRayRadiusKey = config.Bind<KeyboardShortcut>("Display", "XRayRadiusKey", new KeyboardShortcut((KeyCode)288, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Widens the X-ray radius by 10 m, from 40 m up to 80 m, then back to 40 m."); LogSolves = config.Bind<bool>("Debug", "LogSolves", true, "Write to the log each structure solved (size, solver, time, worst piece) and each piece that cracks or falls."); DumpKey = config.Bind<KeyboardShortcut>("Debug", "DumpKey", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Writes the structure under the hammer to BepInEx/fysik-dump.txt (same as the console command 'fysik dump')."); LogGeometry = config.Bind<bool>("Debug", "LogGeometry", true, "Write one line to the log per kind of piece, the first time it is seen: colliders, box, volume and weight. For calibration."); BindMaterial(config, (MaterialType)0, "Wood", 500f, 4f, 4f, 1f, 60f); BindMaterial(config, (MaterialType)3, "HardWood", 600f, 6f, 6f, 1.5f, 65f); BindMaterial(config, (MaterialType)8, "Timberwood", 650f, 8f, 8f, 2f, 70f); BindMaterial(config, (MaterialType)6, "Ancient", 700f, 15f, 15f, 3f, 80f); BindMaterial(config, (MaterialType)1, "Stone", 2400f, 20f, 0.8f, 2f, 30f); BindMaterial(config, (MaterialType)4, "Marble", 2700f, 30f, 1.5f, 3f, 35f); BindMaterial(config, (MaterialType)5, "Ashstone", 2300f, 25f, 1.2f, 2.5f, 30f); BindMaterial(config, (MaterialType)2, "Iron", 1200f, 20f, 20f, 10f, 90f); BindMaterial(config, (MaterialType)7, "Ice", 920f, 5f, 0.5f, 0.8f, 30f); Unknown = BindMaterialSection(config, "Material.Unknown", 600f, 3f, 3f, 0.8f, 50f, "Pieces whose material Fysik does not know (other mods)"); Unknown.Name = "unknown"; } private static void BindMaterial(ConfigFile config, MaterialType type, string name, float density, float compression, float tension, float shear, float slenderness) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) MaterialEntries materialEntries = BindMaterialSection(config, "Material." + name, density, compression, tension, shear, slenderness, name); materialEntries.Name = name; Materials[type] = materialEntries; } private static MaterialEntries BindMaterialSection(ConfigFile config, string section, float density, float compression, float tension, float shear, float slenderness, string what) { return new MaterialEntries { Density = config.Bind<float>(section, "Density", density, Server(what + ": density, kg/m³ (the weight of a piece comes from its volume).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 20000f))), Compression = config.Bind<float>(section, "CompressiveStrength", compression, Server(what + ": compressive strength, MPa.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 1000f))), Tension = config.Bind<float>(section, "TensileStrength", tension, Server(what + ": tensile strength, MPa (also limits bending).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 1000f))), Shear = config.Bind<float>(section, "ShearStrength", shear, Server(what + ": shear strength, MPa.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 1000f))), Slenderness = config.Bind<float>(section, "BucklingSlenderness", slenderness, Server(what + ": slenderness (length / radius of gyration) at which the compressive limit halves. Lower = buckles sooner.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 500f))) }; } private static ConfigDescription Server(string description, AcceptableValueBase range) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown return new ConfigDescription(description + " Enforced by the server.", range, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } }); } } [BepInPlugin("menezesbruno.fysik", "Fysik", "0.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; FysikConfig.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized; Texts.Register(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new FysikCommand()); ApplyPatches(); Log.LogInfo((object)("Fysik 0.1.1 loaded " + $"(Valheim {Version.GetVersionString(false)}, crack warning {FysikConfig.CrackWarningSeconds.Value:0.#} s)")); } private void ApplyPatches() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _harmony = new Harmony("menezesbruno.fysik"); int num = 0; int num2 = 0; Type[] types; try { types = typeof(Plugin).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (!(type == null) && type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0) { try { _harmony.CreateClassProcessor(type).Patch(); num++; } catch (Exception ex2) { num2++; Log.LogWarning((object)("Hook " + type.Name + " not applied, that feature is off (different game version?): " + ex2.GetBaseException().Message)); } } } if (num2 > 0) { Log.LogWarning((object)$"{num2} of {num + num2} hooks could not be applied."); } } private void Update() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) { try { OwnershipHandover.Tick(); return; } catch (Exception e) { Guard.Report("Ownership handover", e); return; } } if ((Object)(object)ZNetScene.instance == (Object)null) { if (StructureManager.Instance.NodeCount > 0) { StructureManager.Instance.Clear(); } return; } try { XRayView.Instance.Tick(); } catch (Exception e2) { Guard.Report("X-ray view", e2); } try { StructureManager.Instance.Tick(FysikConfig.FrameBudgetMs.Value); } catch (Exception e3) { Guard.Report("Structure solver", e3); StructureManager.Instance.AbortJob(); } try { CollapseController.Instance.Tick(); } catch (Exception e4) { Guard.Report("Collapse", e4); } try { FallingDebris.Instance.Tick(); } catch (Exception e5) { Guard.Report("Collapse animation", e5); } try { CrackEffects.Instance.Tick(); } catch (Exception e6) { Guard.Report("Crack effects", e6); } } private void OnDestroy() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private static void OnSettingChanged(object sender, SettingChangedEventArgs args) { string section = args.ChangedSetting.Definition.Section; if (section.StartsWith("Material.") || section == "Structure" || (object)args.ChangedSetting == FysikConfig.DamageWeakens) { SolveAgain(); } } private static void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (args.UpdatedPluginGUIDs.Contains("menezesbruno.fysik")) { Log.LogInfo((object)$"Server config applied: crack warning {FysikConfig.CrackWarningSeconds.Value:0.#} s"); SolveAgain(); } } private static void SolveAgain() { MaterialTable.Invalidate(); StructureManager.Instance.InvalidateAllResults(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "menezesbruno.fysik"; public const string PLUGIN_NAME = "Fysik"; public const string PLUGIN_VERSION = "0.1.1"; } } namespace Fysik.Structure { public enum CrackAction : byte { None, StartCrack, StopCrack, Fall } public static class CrackPolicy { public static CrackAction Decide(double utilization, bool unsupported, bool cracking, double secondsCracking, double warningSeconds, bool cascading, bool ready, bool canFall) { if (!ready) { return CrackAction.None; } if ((!unsupported && !(utilization >= 1.0)) || !canFall) { if (!cracking) { return CrackAction.None; } return CrackAction.StopCrack; } if (unsupported || cascading || warningSeconds <= 0.0) { return CrackAction.Fall; } if (!cracking) { return CrackAction.StartCrack; } if (!(secondsCracking >= warningSeconds)) { return CrackAction.None; } return CrackAction.Fall; } } public static class Damage { public const double FullStrengthHealth = 0.5; public const double MinStrength = 0.25; public static double StrengthFactor(double health) { if (health >= 0.5) { return 1.0; } if (health <= 0.0) { return 0.25; } return 0.25 + 0.75 * health / 0.5; } } public sealed class FrameAnalysis { private struct LinkData { public int A; public int B; public Vec3 ArmA; public Vec3 ArmB; public Mat3 Translational; public Mat3 Rotational; } private const int Dof = 6; private const int Block = 36; private readonly StructureModel _model; private readonly int _n; private readonly LinkData[] _links; private readonly double[] _diagonal; private readonly double[] _offDiagonal; private readonly double[] _preconditioner; private readonly double[] _x; private readonly double[] _r; private readonly double[] _z; private readonly double[] _p; private readonly double[] _q; private readonly double[] _f; private readonly double _tolerance; private readonly int _maxIterations; private double _rz; private bool _finished; private const int LinksPerStep = 16; private int _assembled; private bool _ready; private readonly double[] _ta = new double[36]; private readonly double[] _tb = new double[36]; private readonly double[] _d = new double[36]; private readonly double[] _dta = new double[36]; private readonly double[] _dtb = new double[36]; private readonly double[] _tmp = new double[36]; public int Iterations { get; private set; } public bool Converged { get; private set; } public double RelativeResidual { get; private set; } public double[] Displacements => _x; public FrameAnalysis(StructureModel model, double tolerance = 1E-06, int maxIterations = 0) { _model = model; _n = model.Bodies.Count; _tolerance = tolerance; _maxIterations = ((maxIterations > 0) ? maxIterations : Math.Max(500, 24 * _n)); _links = new LinkData[model.Links.Count]; _diagonal = new double[_n * 36]; _offDiagonal = new double[model.Links.Count * 36]; _preconditioner = new double[_n * 36]; _x = new double[_n * 6]; _r = new double[_n * 6]; _z = new double[_n * 6]; _p = new double[_n * 6]; _q = new double[_n * 6]; _f = new double[_n * 6]; for (int i = 0; i < _n; i++) { Vec3 vec = model.Gravity * model.Bodies[i].Mass; _f[i * 6] = vec.X; _f[i * 6 + 1] = vec.Y; _f[i * 6 + 2] = vec.Z; } } public bool Iterate(int steps) { if (!_ready) { int num = Math.Min(_links.Length, _assembled + steps * 16); while (_assembled < num) { AssembleLink(_assembled); _assembled++; } if (_assembled < _links.Length) { return false; } Initialize(); return _finished; } for (int i = 0; i < steps; i++) { if (_finished) { break; } Multiply(_p, _q); double num2 = Dot(_p, _q); if (num2 <= 0.0 || double.IsNaN(num2)) { _finished = true; break; } double num3 = _rz / num2; for (int j = 0; j < _x.Length; j++) { _x[j] += num3 * _p[j]; _r[j] -= num3 * _q[j]; } Iterations++; UpdateResidual(); if (_finished) { break; } ApplyPreconditioner(_r, _z); double num4 = Dot(_r, _z); double num5 = num4 / _rz; _rz = num4; for (int k = 0; k < _p.Length; k++) { _p[k] = _z[k] + num5 * _p[k]; } } return _finished; } public void Solve() { while (!Iterate(64)) { } } public List<ContactLoad>[] ContactLoads() { List<ContactLoad>[] array = new List<ContactLoad>[_n]; for (int i = 0; i < _n; i++) { array[i] = new List<ContactLoad>(); } LinkData[] links = _links; for (int j = 0; j < links.Length; j++) { LinkData linkData = links[j]; Vec3 vec = Translation(linkData.A); Vec3 vec2 = Rotation(linkData.A); Vec3 vec3 = -(vec + Vec3.Cross(vec2, linkData.ArmA)); Vec3 vec4 = -vec2; if (linkData.B >= 0) { Vec3 vec5 = Translation(linkData.B); Vec3 vec6 = Rotation(linkData.B); vec3 += vec5 + Vec3.Cross(vec6, linkData.ArmB); vec4 += vec6; } Vec3 vec7 = linkData.Translational * vec3; Vec3 vec8 = linkData.Rotational * vec4; Vec3 point = _model.Bodies[linkData.A].Center + linkData.ArmA; array[linkData.A].Add(new ContactLoad { Point = point, Force = vec7, Moment = vec8 }); if (linkData.B >= 0) { array[linkData.B].Add(new ContactLoad { Point = point, Force = -vec7, Moment = -vec8 }); } } return array; } public BodyResult[] Evaluate() { List<ContactLoad>[] array = ContactLoads(); BodyResult[] array2 = new BodyResult[_n]; for (int i = 0; i < _n; i++) { array2[i] = Mechanics.Evaluate(_model.Bodies[i], array[i], _model.Gravity); } return array2; } private Vec3 Translation(int i) { return new Vec3(_x[i * 6], _x[i * 6 + 1], _x[i * 6 + 2]); } private Vec3 Rotation(int i) { return new Vec3(_x[i * 6 + 3], _x[i * 6 + 4], _x[i * 6 + 5]); } private void UpdateResidual() { double num = Math.Sqrt(Dot(_f, _f)); RelativeResidual = ((num > 0.0) ? (Math.Sqrt(Dot(_r, _r)) / num) : 0.0); if (RelativeResidual <= _tolerance) { Converged = true; _finished = true; } else if (Iterations >= _maxIterations) { _finished = true; } } private void AssembleLink(int li) { Link link = _model.Links[li]; Body body = _model.Bodies[link.A]; LinkData linkData = new LinkData { A = link.A, B = link.B, ArmA = link.Point - body.Center }; Mechanics.SegmentStiffness(body, link.Point, out var translational, out var rotational); if (link.IsGround) { linkData.Translational = translational; linkData.Rotational = rotational; } else { Body body2 = _model.Bodies[link.B]; linkData.ArmB = link.Point - body2.Center; Mechanics.SegmentStiffness(body2, link.Point, out var translational2, out var rotational2); linkData.Translational = Mechanics.Series(translational, translational2); linkData.Rotational = Mechanics.Series(rotational, rotational2); } _links[li] = linkData; Array.Clear(_ta, 0, 36); SetBlock(_ta, 0, 0, -Mat3.Identity); SetBlock(_ta, 0, 3, Mat3.Skew(linkData.ArmA)); SetBlock(_ta, 3, 3, -Mat3.Identity); Array.Clear(_d, 0, 36); SetBlock(_d, 0, 0, linkData.Translational); SetBlock(_d, 3, 3, linkData.Rotational); Mul6(_d, _ta, _dta); MulT6(_ta, _dta, _tmp); Add6(_diagonal, link.A * 36, _tmp); if (!link.IsGround) { Array.Clear(_tb, 0, 36); SetBlock(_tb, 0, 0, Mat3.Identity); SetBlock(_tb, 0, 3, -Mat3.Skew(linkData.ArmB)); SetBlock(_tb, 3, 3, Mat3.Identity); Mul6(_d, _tb, _dtb); MulT6(_tb, _dtb, _tmp); Add6(_diagonal, link.B * 36, _tmp); MulT6(_ta, _dtb, _tmp); Array.Copy(_tmp, 0, _offDiagonal, li * 36, 36); } } private void Initialize() { for (int i = 0; i < _n; i++) { Cholesky6(_diagonal, i * 36, _preconditioner, i * 36); } Multiply(_x, _q); for (int j = 0; j < _r.Length; j++) { _r[j] = _f[j] - _q[j]; } ApplyPreconditioner(_r, _z); Array.Copy(_z, _p, _z.Length); _rz = Dot(_r, _z); _ready = true; UpdateResidual(); } private void Multiply(double[] x, double[] y) { for (int i = 0; i < _n; i++) { MulVec6(_diagonal, i * 36, x, i * 6, y, i * 6, overwrite: true); } for (int j = 0; j < _links.Length; j++) { int a = _links[j].A; int b = _links[j].B; if (b >= 0) { MulVec6(_offDiagonal, j * 36, x, b * 6, y, a * 6, overwrite: false); MulVecT6(_offDiagonal, j * 36, x, a * 6, y, b * 6); } } } private void ApplyPreconditioner(double[] r, double[] z) { for (int i = 0; i < _n; i++) { CholeskySolve6(_preconditioner, i * 36, r, z, i * 6); } } private static double Dot(double[] a, double[] b) { double num = 0.0; for (int i = 0; i < a.Length; i++) { num += a[i] * b[i]; } return num; } private static void SetBlock(double[] m, int row, int col, Mat3 b) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { m[(row + i) * 6 + col + j] = b[i, j]; } } } private static void Mul6(double[] a, double[] b, double[] result) { for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { double num = 0.0; for (int k = 0; k < 6; k++) { num += a[i * 6 + k] * b[k * 6 + j]; } result[i * 6 + j] = num; } } } private static void MulT6(double[] a, double[] b, double[] result) { for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { double num = 0.0; for (int k = 0; k < 6; k++) { num += a[k * 6 + i] * b[k * 6 + j]; } result[i * 6 + j] = num; } } } private static void Add6(double[] target, int offset, double[] m) { for (int i = 0; i < 36; i++) { target[offset + i] += m[i]; } } private static void MulVec6(double[] m, int mo, double[] x, int xo, double[] y, int yo, bool overwrite) { for (int i = 0; i < 6; i++) { int num = mo + i * 6; double num2 = m[num] * x[xo] + m[num + 1] * x[xo + 1] + m[num + 2] * x[xo + 2] + m[num + 3] * x[xo + 3] + m[num + 4] * x[xo + 4] + m[num + 5] * x[xo + 5]; y[yo + i] = (overwrite ? num2 : (y[yo + i] + num2)); } } private static void MulVecT6(double[] m, int mo, double[] x, int xo, double[] y, int yo) { for (int i = 0; i < 6; i++) { double num = 0.0; for (int j = 0; j < 6; j++) { num += m[mo + j * 6 + i] * x[xo + j]; } y[yo + i] += num; } } private static void Cholesky6(double[] a, int ao, double[] l, int lo) { for (int i = 0; i < 6; i++) { for (int j = 0; j <= i; j++) { double num = a[ao + i * 6 + j]; for (int k = 0; k < j; k++) { num -= l[lo + i * 6 + k] * l[lo + j * 6 + k]; } if (i == j) { l[lo + i * 6 + i] = Math.Sqrt(Math.Max(num, 1E-30)); } else { l[lo + i * 6 + j] = num / l[lo + j * 6 + j]; } } } } private static void CholeskySolve6(double[] l, int lo, double[] b, double[] x, int o) { for (int i = 0; i < 6; i++) { double num = b[o + i]; for (int j = 0; j < i; j++) { num -= l[lo + i * 6 + j] * x[o + j]; } x[o + i] = num / l[lo + i * 6 + i]; } for (int num2 = 5; num2 >= 0; num2--) { double num3 = x[o + num2]; for (int k = num2 + 1; k < 6; k++) { num3 -= l[lo + k * 6 + num2] * x[o + k]; } x[o + num2] = num3 / l[lo + num2 * 6 + num2]; } } } public static class LoadPathAnalysis { private struct Neighbour { public int Other; public Vec3 Point; } private struct Support { public int Other; public Vec3 Point; public double Weight; } public static BodyResult[] Run(StructureModel model) { int count = model.Bodies.Count; BodyResult[] array = new BodyResult[count]; List<Neighbour>[] array2 = new List<Neighbour>[count]; List<Vec3>[] array3 = new List<Vec3>[count]; for (int i = 0; i < count; i++) { array2[i] = new List<Neighbour>(); array3[i] = new List<Vec3>(); } foreach (Link link in model.Links) { if (link.IsGround) { array3[link.A].Add(link.Point); continue; } array2[link.A].Add(new Neighbour { Other = link.B, Point = link.Point }); array2[link.B].Add(new Neighbour { Other = link.A, Point = link.Point }); } int[] level = new int[count]; Queue<int> queue = new Queue<int>(); for (int j = 0; j < count; j++) { level[j] = ((array3[j].Count <= 0) ? int.MaxValue : 0); if (level[j] == 0) { queue.Enqueue(j); } } while (queue.Count > 0) { int num = queue.Dequeue(); foreach (Neighbour item in array2[num]) { int other = item.Other; if (level[other] == int.MaxValue) { level[other] = level[num] + 1; queue.Enqueue(other); } } } int[] array4 = new int[count]; for (int k = 0; k < count; k++) { array4[k] = k; } Array.Sort(array4, (int a, int b) => (level[a] == level[b]) ? a.CompareTo(b) : level[b].CompareTo(level[a])); double length = model.Gravity.Length; Vec3 down = model.Gravity.Normalized(); double[] array5 = new double[count]; Vec3[] array6 = new Vec3[count]; for (int num2 = 0; num2 < count; num2++) { array5[num2] = model.Bodies[num2].Mass * length; array6[num2] = model.Bodies[num2].Center * array5[num2]; } List<Support> list = new List<Support>(); int[] array7 = array4; foreach (int num4 in array7) { if (level[num4] == int.MaxValue) { array[num4] = BodyResult.Unsupported; continue; } Body body = model.Bodies[num4]; list.Clear(); if (level[num4] == 0) { foreach (Vec3 item2 in array3[num4]) { list.Add(new Support { Other = -1, Point = item2, Weight = SupportWeight(body, item2, down) }); } } else { foreach (Neighbour item3 in array2[num4]) { if (level[item3.Other] < level[num4]) { list.Add(new Support { Other = item3.Other, Point = item3.Point, Weight = SupportWeight(body, item3.Point, down) }); } } } double num5 = 0.0; Vec3 zero = Vec3.Zero; foreach (Support item4 in list) { num5 += item4.Weight; zero += item4.Point * item4.Weight; } zero /= num5; Vec3 vec = array6[num4] / array5[num4]; foreach (Support item5 in list) { if (item5.Other >= 0) { double num6 = array5[num4] * item5.Weight / num5; array5[item5.Other] += num6; array6[item5.Other] += vec * num6; } } array[num4] = Check(body, array5[num4], vec, zero, list, down); } return array; } private static double SupportWeight(Body body, Vec3 point, Vec3 down) { Vec3 a = (point - body.Center).Normalized(); return 0.25 + Math.Max(0.0, Vec3.Dot(a, down)); } private static Vec3 Horizontal(Vec3 v, Vec3 down) { return v - down * Vec3.Dot(v, down); } private static BodyResult Check(Body body, double load, Vec3 centroid, Vec3 supportPoint, List<Support> supports, Vec3 down) { Vec3 vec = down * load; int num = MostAligned(body, down); BodyResult result = Section(body, num, vec, Vec3.Zero, (num == Mechanics.LongestAxis(body)) ? Mechanics.BucklingFactor(body) : 1.0); Vec3 vec2 = Horizontal(centroid - supportPoint, down); double num2 = 0.0; for (int i = 0; i < supports.Count; i++) { for (int j = i + 1; j < supports.Count; j++) { num2 = Math.Max(num2, Horizontal(supports[i].Point - supports[j].Point, down).Length); } } if (vec2.Length > 1E-06 || num2 > 1E-06) { Vec3 vec3 = ((vec2.Length > 1E-06) ? vec2.Normalized() : Horizontal(supports[1].Point - supports[0].Point, down).Normalized()); Vec3 moment = Vec3.Cross(vec3, vec) * (vec2.Length + num2 / 8.0); int axis = MostAligned(body, vec3); BodyResult bodyResult = Section(body, axis, vec, moment, 1.0); if (bodyResult.Utilization > result.Utilization) { result = bodyResult; } } return result; } private static BodyResult Section(Body body, int axis, Vec3 force, Vec3 moment, double buckling) { Vec3 vec = body.Axis(axis); int i = (axis + 1) % 3; int i2 = (axis + 2) % 3; if (Vec3.Dot(vec, force) > 0.0) { vec = -vec; } return Mechanics.SectionCheck(body.Material, force, moment, vec, body.Axis(i), body.Axis(i2), body.Size[i], body.Size[i2], buckling, body.StrengthFactor); } private static int MostAligned(Body body, Vec3 dir) { int result = 0; double num = -1.0; for (int i = 0; i < 3; i++) { double num2 = Math.Abs(Vec3.Dot(body.Axis(i), dir)); if (num2 > num) { num = num2; result = i; } } return result; } } public readonly struct Vec3 { public readonly double X; public readonly double Y; public readonly double Z; public static readonly Vec3 Zero = new Vec3(0.0, 0.0, 0.0); public static readonly Vec3 UnitX = new Vec3(1.0, 0.0, 0.0); public static readonly Vec3 UnitY = new Vec3(0.0, 1.0, 0.0); public static readonly Vec3 UnitZ = new Vec3(0.0, 0.0, 1.0); public double this[int i] => i switch { 1 => Y, 0 => X, _ => Z, }; public double LengthSquared => X * X + Y * Y + Z * Z; public double Length => Math.Sqrt(LengthSquared); public Vec3(double x, double y, double z) { X = x; Y = y; Z = z; } public static Vec3 operator +(Vec3 a, Vec3 b) { return new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); } public static Vec3 operator -(Vec3 a, Vec3 b) { return new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); } public static Vec3 operator -(Vec3 a) { return new Vec3(0.0 - a.X, 0.0 - a.Y, 0.0 - a.Z); } public static Vec3 operator *(Vec3 a, double s) { return new Vec3(a.X * s, a.Y * s, a.Z * s); } public static Vec3 operator *(double s, Vec3 a) { return new Vec3(a.X * s, a.Y * s, a.Z * s); } public static Vec3 operator /(Vec3 a, double s) { return new Vec3(a.X / s, a.Y / s, a.Z / s); } public static double Dot(Vec3 a, Vec3 b) { return a.X * b.X + a.Y * b.Y + a.Z * b.Z; } public static Vec3 Cross(Vec3 a, Vec3 b) { return new Vec3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); } public Vec3 Normalized() { double length = Length; if (!(length > 1E-12)) { return Zero; } return this / length; } public override string ToString() { return $"({X:0.###}, {Y:0.###}, {Z:0.###})"; } } public struct Mat3 { public double M00; public double M01; public double M02; public double M10; public double M11; public double M12; public double M20; public double M21; public double M22; public static readonly Mat3 Identity = new Mat3 { M00 = 1.0, M11 = 1.0, M22 = 1.0 }; public double this[int r, int c] => (r * 3 + c) switch { 0 => M00, 1 => M01, 2 => M02, 3 => M10, 4 => M11, 5 => M12, 6 => M20, 7 => M21, _ => M22, }; public double Determinant => M00 * (M11 * M22 - M12 * M21) - M01 * (M10 * M22 - M12 * M20) + M02 * (M10 * M21 - M11 * M20); public static Mat3 Outer(Vec3 a, double s) { return new Mat3 { M00 = s * a.X * a.X, M01 = s * a.X * a.Y, M02 = s * a.X * a.Z, M10 = s * a.Y * a.X, M11 = s * a.Y * a.Y, M12 = s * a.Y * a.Z, M20 = s * a.Z * a.X, M21 = s * a.Z * a.Y, M22 = s * a.Z * a.Z }; } public static Mat3 Skew(Vec3 r) { return new Mat3 { M01 = 0.0 - r.Z, M02 = r.Y, M10 = r.Z, M12 = 0.0 - r.X, M20 = 0.0 - r.Y, M21 = r.X }; } public static Mat3 operator +(Mat3 a, Mat3 b) { return new Mat3 { M00 = a.M00 + b.M00, M01 = a.M01 + b.M01, M02 = a.M02 + b.M02, M10 = a.M10 + b.M10, M11 = a.M11 + b.M11, M12 = a.M12 + b.M12, M20 = a.M20 + b.M20, M21 = a.M21 + b.M21, M22 = a.M22 + b.M22 }; } public static Mat3 operator -(Mat3 a) { return new Mat3 { M00 = 0.0 - a.M00, M01 = 0.0 - a.M01, M02 = 0.0 - a.M02, M10 = 0.0 - a.M10, M11 = 0.0 - a.M11, M12 = 0.0 - a.M12, M20 = 0.0 - a.M20, M21 = 0.0 - a.M21, M22 = 0.0 - a.M22 }; } public static Mat3 operator *(Mat3 a, Mat3 b) { return new Mat3 { M00 = a.M00 * b.M00 + a.M01 * b.M10 + a.M02 * b.M20, M01 = a.M00 * b.M01 + a.M01 * b.M11 + a.M02 * b.M21, M02 = a.M00 * b.M02 + a.M01 * b.M12 + a.M02 * b.M22, M10 = a.M10 * b.M00 + a.M11 * b.M10 + a.M12 * b.M20, M11 = a.M10 * b.M01 + a.M11 * b.M11 + a.M12 * b.M21, M12 = a.M10 * b.M02 + a.M11 * b.M12 + a.M12 * b.M22, M20 = a.M20 * b.M00 + a.M21 * b.M10 + a.M22 * b.M20, M21 = a.M20 * b.M01 + a.M21 * b.M11 + a.M22 * b.M21, M22 = a.M20 * b.M02 + a.M21 * b.M12 + a.M22 * b.M22 }; } public static Vec3 operator *(Mat3 a, Vec3 v) { return new Vec3(a.M00 * v.X + a.M01 * v.Y + a.M02 * v.Z, a.M10 * v.X + a.M11 * v.Y + a.M12 * v.Z, a.M20 * v.X + a.M21 * v.Y + a.M22 * v.Z); } public Mat3 Transposed() { return new Mat3 { M00 = M00, M01 = M10, M02 = M20, M10 = M01, M11 = M11, M12 = M21, M20 = M02, M21 = M12, M22 = M22 }; } public Mat3 Inverse() { double determinant = Determinant; if (Math.Abs(determinant) < 1E-300) { throw new InvalidOperationException("Singular 3x3 matrix"); } double num = 1.0 / determinant; return new Mat3 { M00 = (M11 * M22 - M12 * M21) * num, M01 = (M02 * M21 - M01 * M22) * num, M02 = (M01 * M12 - M02 * M11) * num, M10 = (M12 * M20 - M10 * M22) * num, M11 = (M00 * M22 - M02 * M20) * num, M12 = (M02 * M10 - M00 * M12) * num, M20 = (M10 * M21 - M11 * M20) * num, M21 = (M01 * M20 - M00 * M21) * num, M22 = (M00 * M11 - M01 * M10) * num }; } } public struct ContactLoad { public Vec3 Point; public Vec3 Force; public Vec3 Moment; } public static class Mechanics { private const double ShearCorrection = 5.0 / 6.0; private const double PlateAspect = 4.0; private const double MinSegmentLength = 0.05; public static void SegmentStiffness(Body body, Vec3 point, out Mat3 translational, out Mat3 rotational) { Vec3 a = point - body.Center; int num = 0; double num2 = -1.0; for (int i = 0; i < 3; i++) { double num3 = Math.Abs(Vec3.Dot(a, body.Axis(i))); if (num3 > num2) { num2 = num3; num = i; } } if (a.LengthSquared < 1E-12) { num = SmallestAxis(body); } double num4 = Math.Max(num2, 0.05); int i2 = (num + 1) % 3; int i3 = (num + 2) % 3; double num5 = body.Size[i2]; double num6 = body.Size[i3]; Vec3 a2 = body.Axis(num); Vec3 a3 = body.Axis(i2); Vec3 a4 = body.Axis(i3); double num7 = num5 * num6; double num8 = num5 * num6 * num6 * num6 / 12.0; double num9 = num6 * num5 * num5 * num5 / 12.0; double num10 = TorsionConstant(num5, num6); double elasticity = body.Material.Elasticity; double shearModulus = body.Material.ShearModulus; double s = 5.0 / 6.0 * shearModulus * num7 / num4; translational = Mat3.Outer(a2, elasticity * num7 / num4) + Mat3.Outer(a3, s) + Mat3.Outer(a4, s); rotational = Mat3.Outer(a2, shearModulus * num10 / num4) + Mat3.Outer(a3, elasticity * num8 / num4) + Mat3.Outer(a4, elasticity * num9 / num4); } public static Mat3 Series(Mat3 k1, Mat3 k2) { Mat3 mat = k1.Inverse(); Mat3 mat2 = k2.Inverse(); return (mat + mat2).Inverse(); } public static double TorsionConstant(double s1, double s2) { double num = Math.Max(s1, s2); double num2 = Math.Min(s1, s2); double num3 = num2 / num; return num * num2 * num2 * num2 * (1.0 / 3.0 - 0.21 * num3 * (1.0 - num3 * num3 * num3 * num3 / 12.0)); } public static int SmallestAxis(Body body) { Vec3 size = body.Size; if (!(size.X <= size.Y) || !(size.X <= size.Z)) { if (!(size.Y <= size.Z)) { return 2; } return 1; } return 0; } public static int LongestAxis(Body body) { Vec3 size = body.Size; if (!(size.X >= size.Y) || !(size.X >= size.Z)) { if (!(size.Y >= size.Z)) { return 2; } return 1; } return 0; } public static double BucklingFactor(Body body) { int num = LongestAxis(body); double num2 = body.Size[num]; double val = Math.Min(body.Size[(num + 1) % 3], body.Size[(num + 2) % 3]) / Math.Sqrt(12.0); double num3 = num2 / Math.Max(val, 1E-06) / Math.Max(body.Material.BucklingSlenderness, 1E-06); return 1.0 / (1.0 + num3 * num3); } public static BodyResult Evaluate(Body body, IReadOnlyList<ContactLoad> contacts, Vec3 gravity) { Vec3 vec = gravity * body.Mass; int num = LongestAxis(body); double num2 = BucklingFactor(body); BodyResult result = new BodyResult { Mode = StressMode.None }; List<double> list = new List<double>(contacts.Count * 2 + 1); for (int i = 0; i < 3; i++) { Vec3 vec2 = body.Axis(i); double num3 = body.Size[i]; double num4 = num3 / 2.0; double num5 = 0.001 * num3 + 1E-06; list.Clear(); list.Add(0.0); foreach (ContactLoad contact in contacts) { double num6 = Vec3.Dot(contact.Point - body.Center, vec2); list.Add(Clamp(num6 - num5, 0.0 - num4 + num5, num4 - num5)); list.Add(Clamp(num6 + num5, 0.0 - num4 + num5, num4 - num5)); } int i2 = (i + 1) % 3; int i3 = (i + 2) % 3; Vec3 e = body.Axis(i2); Vec3 e2 = body.Axis(i3); double s = body.Size[i2]; double s2 = body.Size[i3]; double buckling = ((i == num) ? num2 : 1.0); foreach (double item in list) { Vec3 vec3 = body.Center + vec2 * item; Vec3 zero = Vec3.Zero; Vec3 zero2 = Vec3.Zero; foreach (ContactLoad contact2 in contacts) { if (!(Vec3.Dot(contact2.Point - body.Center, vec2) <= item)) { zero += contact2.Force; zero2 += contact2.Moment + Vec3.Cross(contact2.Point - vec3, contact2.Force); } } double num7 = ((num3 > 0.0) ? ((num4 - item) / num3) : 0.0); Vec3 vec4 = vec * num7; Vec3 vec5 = body.Center + vec2 * ((item + num4) / 2.0); zero += vec4; zero2 += Vec3.Cross(vec5 - vec3, vec4); BodyResult bodyResult = SectionCheck(body.Material, zero, zero2, vec2, e, e2, s, s2, buckling, body.StrengthFactor); if (bodyResult.Utilization > result.Utilization || result.Mode == StressMode.None) { result = bodyResult; } } } return result; } public static BodyResult SectionCheck(MaterialProps m, Vec3 force, Vec3 moment, Vec3 e, Vec3 e1, Vec3 e2, double s1, double s2, double buckling, double strengthFactor = 1.0) { double num = Math.Max(strengthFactor, 0.001); double num2 = s1 * s2; double num3 = Vec3.Dot(force, e); double length = (force - e * num3).Length; double num4 = Math.Abs(Vec3.Dot(moment, e)); double num5 = Math.Abs(Vec3.Dot(moment, e1)); double num6 = Math.Abs(Vec3.Dot(moment, e2)); double num7 = s1 * s2 * s2 / 6.0; double num8 = s2 * s1 * s1 / 6.0; double num9 = num3 / num2; double num10 = 1.5 * length / num2; double num11 = Math.Max(s1, s2); double num12 = Math.Min(s1, s2); double num18; double num19; if (num11 >= 4.0 * num12) { double num13 = ((s1 >= s2) ? num5 : num6); double num14 = ((s1 >= s2) ? num6 : num5); double num15 = num13 / num11; double num16 = num4 / num11; double num17 = Math.Abs(num15) / 2.0 + Math.Sqrt(num15 * num15 / 4.0 + num16 * num16); num18 = 6.0 * num17 / (num12 * num12) + num14 / (num12 * num11 * num11 / 6.0); num19 = 0.0; } else { num18 = num5 / num7 + num6 / num8; num19 = num4 * (3.0 + 1.8 * num12 / num11) / (num11 * num12 * num12); } double num20 = num10 + num19; double num21 = Math.Max(0.0, 0.0 - num9) / Math.Max(buckling, 1E-06) - Math.Max(0.0, num9) + num18; double num22 = num9 + num18; double num23 = num21 / (m.CompressiveStrength * num); double num24 = num22 / (m.TensileStrength * num); double num25 = num20 / (m.ShearStrength * num); bool flag = num18 > Math.Abs(num9); BodyResult result = new BodyResult { AxialForce = num3, BendingMoment = Math.Sqrt(num5 * num5 + num6 * num6), Torque = num4 }; if (num23 >= num24 && num23 >= num25) { result.Utilization = Math.Max(0.0, num23); result.Mode = ((!flag) ? StressMode.Compression : StressMode.Bending); } else if (num24 >= num25) { result.Utilization = num24; result.Mode = (flag ? StressMode.Bending : StressMode.Tension); } else { result.Utilization = num25; result.Mode = ((num19 > num10) ? StressMode.Torsion : StressMode.Shear); } return result; } private static double Clamp(double v, double lo, double hi) { if (!(v < lo)) { if (!(v > hi)) { return v; } return hi; } return lo; } } public enum SolverKind : byte { None, Frame, LoadPath } public sealed class StructureAnalysis { public const double DefaultTolerance = 1E-06; private const int BodiesPerStep = 8; private readonly StructureModel _model; private readonly FrameAnalysis _frame; private List<ContactLoad>[] _loads; private BodyResult[] _pending; private int _evaluated; public SolverKind Solver { get; } public BodyResult[] Results { get; private set; } public bool Finished => Results != null; public int Iterations => _frame?.Iterations ?? 0; public bool Converged => _frame?.Converged ?? true; public StructureAnalysis(StructureModel model, int maxFrameBodies) { _model = model; bool flag = false; foreach (Link link in model.Links) { flag |= link.IsGround; } if (!flag || model.Bodies.Count == 0) { Solver = SolverKind.None; Results = new BodyResult[model.Bodies.Count]; for (int i = 0; i < Results.Length; i++) { Results[i] = BodyResult.Unsupported; } } else if (model.Bodies.Count <= maxFrameBodies) { Solver = SolverKind.Frame; _frame = new FrameAnalysis(model); } else { Solver = SolverKind.LoadPath; } } public void RunToCompletion(Func<bool> cancelled) { while (!Finished && !cancelled()) { Step(16); } } public bool Step(int units) { if (Finished) { return true; } units = Math.Max(1, units); if (Solver == SolverKind.LoadPath) { Results = LoadPathAnalysis.Run(_model); return true; } if (_loads == null) { if (!_frame.Iterate(units)) { return false; } _loads = _frame.ContactLoads(); _pending = new BodyResult[_model.Bodies.Count]; return false; } int num = Math.Min(_pending.Length, _evaluated + units * 8); while (_evaluated < num) { _pending[_evaluated] = Mechanics.Evaluate(_model.Bodies[_evaluated], _loads[_evaluated], _model.Gravity); _evaluated++; } if (_evaluated == _pending.Length) { Results = _pending; } return Finished; } } public sealed class MaterialProps { public string Name = "default"; public double Density = 500.0; public double Elasticity = 10000000000.0; public double ShearModulus = 600000000.0; public double CompressiveStrength = 4000000.0; public double TensileStrength = 4000000.0; public double ShearStrength = 1000000.0; public double BucklingSlenderness = 60.0; } public sealed class Body { public Vec3 Center; public Vec3 AxisX = Vec3.UnitX; public Vec3 AxisY = Vec3.UnitY; public Vec3 AxisZ = Vec3.UnitZ; public Vec3 Size; public double Mass; public MaterialProps Material; public double StrengthFactor = 1.0; public Vec3 Axis(int i) { return i switch { 1 => AxisY, 0 => AxisX, _ => AxisZ, }; } public double Extent(int i) { return Size[i]; } } public readonly struct Link { public const int Ground = -1; public readonly int A; public readonly int B; public readonly Vec3 Point; public bool IsGround => B == -1; public Link(int a, int b, Vec3 point) { if (a < 0) { throw new ArgumentOutOfRangeException("a", "The first body of a link cannot be the ground"); } A = a; B = b; Point = point; } } public sealed class StructureModel { public readonly List<Body> Bodies = new List<Body>(); public readonly List<Link> Links = new List<Link>(); public Vec3 Gravity = new Vec3(0.0, -9.81, 0.0); public int AddBody(Body body) { Bodies.Add(body); return Bodies.Count - 1; } public void Connect(int a, int b, Vec3 point) { Links.Add(new Link(a, b, point)); } public void Ground(int a, Vec3 point) { Links.Add(new Link(a, -1, point)); } } public enum StressMode : byte { None, Compression, Tension, Bending, Shear, Unsupported, Torsion } public struct BodyResult { public double Utilization; public StressMode Mode; public double AxialForce; public double BendingMoment; public double Torque; public static BodyResult Unsupported => new BodyResult { Utilization = double.PositiveInfinity, Mode = StressMode.Unsupported }; } } namespace Fysik.Game { internal sealed class CollapseController { internal static readonly int CrackKey = StringExtensionMethods.GetStableHashCode("fysik_crack"); private const float RequestInterval = 1f; private const float EvaluateInterval = 0.25f; private const int MaxNewPiecesPerPass = 300; private float _nextRequest; private float _nextEvaluate; private readonly Dictionary<Island, bool> _ready = new Dictionary<Island, bool>(); private readonly List<PieceNode> _falling = new List<PieceNode>(); private readonly HashSet<PieceNode> _fallingSet = new HashSet<PieceNode>(); private readonly HashSet<Island> _fallingIslands = new HashSet<Island>(); private readonly HashSet<PieceNode> _grounded = new HashSet<PieceNode>(); private readonly Queue<PieceNode> _queue = new Queue<PieceNode>(); public static CollapseController Instance { get; } = new CollapseController(); public void Tick() { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsDedicated() && !((Object)(object)ZoneSystem.instance == (Object)null)) { bool flag = FysikConfig.Mode.Value == StructureMode.Physics; float time = Time.time; if (flag && time >= _nextRequest) { _nextRequest = time + 1f; RequestOwnedStructures(); } if (time >= _nextEvaluate) { _nextEvaluate = time + 0.25f; Evaluate(flag); } } } private static void RequestOwnedStructures() { StructureManager instance = StructureManager.Instance; int num = 0; foreach (WearNTear allInstance in WearNTear.GetAllInstances()) { if (Owned(allInstance) && !PieceNode.KeepsVanilla(allInstance) && (instance.Knows(allInstance) || num++ < 300)) { instance.Request(allInstance); } } } private void Evaluate(bool physics) { _ready.Clear(); _falling.Clear(); long ticks = ZNet.instance.GetTime().Ticks; double warningSeconds = FysikConfig.CrackWarningSeconds.Value; bool worldAllowsFalls = !ZoneSystem.instance.GetGlobalKey((GlobalKeys)37); StructureManager instance = StructureManager.Instance; foreach (WearNTear allInstance in WearNTear.GetAllInstances()) { if (!Owned(allInstance)) { continue; } bool flag = PieceNode.KeepsVanilla(allInstance); PieceNode node; bool flag2 = instance.TryGetNode(allInstance, out node); if (!flag2 && !flag) { continue; } ZDO zDO = allInstance.m_nview.GetZDO(); long num = zDO.GetLong(CrackKey, 0L); if (flag2) { node.CrackTicks = num; } if (!physics || flag) { if (num != 0L) { SetCrack(zDO, node, 0L); } } else if (node.Attachment) { if (node.ContactsValid && !node.HasSupport && AttachmentReady(node) && CanFall(allInstance, worldAllowsFalls)) { _falling.Add(node); } } else { if (!node.Structural || !node.HasResult || node.Island == null || node.Island.Stale) { continue; } bool flag3 = IsReady(node.Island); if (!flag3) { node.Watched = false; continue; } if (!node.Watched) { node.Watched = true; if (num != 0L) { SetCrack(zDO, node, num = ticks); } } bool canFall = CanFall(allInstance, worldAllowsFalls); double secondsCracking = ((num != 0L) ? ((double)(ticks - num) / 10000000.0) : 0.0); switch (CrackPolicy.Decide(node.Result.Utilization, node.Result.Mode == StressMode.Unsupported, num != 0, secondsCracking, warningSeconds, node.Island.Cascading, flag3, canFall)) { case CrackAction.StartCrack: SetCrack(zDO, node, ticks); if (FysikConfig.LogSolves.Value) { Plugin.Log.LogInfo((object)(node.Name + " is cracking (" + StructureManager.Describe(node.Result) + ")")); } break; case CrackAction.StopCrack: SetCrack(zDO, node, 0L); if (FysikConfig.LogSolves.Value) { Plugin.Log.LogInfo((object)(node.Name + " stopped cracking (" + StructureManager.Describe(node.Result) + ")")); } break; case CrackAction.Fall: _falling.Add(node); break; } } } if (_falling.Count == 0) { return; } int count = _falling.Count; AddUnsupported(worldAllowsFalls); FallingDebris.Announce(_falling); for (int i = 0; i < _falling.Count; i++) { PieceNode pieceNode = _falling[i]; if (pieceNode.Alive) { pieceNode.FellByFysik = true; if (FysikConfig.LogSolves.Value) { Plugin.Log.LogInfo((object)(pieceNode.Name + " gave way (" + ((i < count && !pieceNode.Attachment) ? (StructureManager.Describe(pieceNode.Result) + (pieceNode.Island.Cascading ? ", cascade" : "")) : "lost its support") + ")")); } pieceNode.Wnt.ApplyDamage(pieceNode.Wnt.m_health * 10f + 1000f, (HitData)null); } } } private static bool CanFall(WearNTear wnt, bool worldAllowsFalls) { if (worldAllowsFalls && wnt.m_noSupportWear && ((Object)(object)wnt.m_piece == (Object)null || wnt.m_piece.CanBeRemoved())) { return !PieceNode.KeepsVanilla(wnt); } return false; } private void AddUnsupported(bool worldAllowsFalls) { _fallingSet.Clear(); _fallingIslands.Clear(); foreach (PieceNode item in _falling) { _fallingSet.Add(item); if (item.Island != null) { _fallingIslands.Add(item.Island); } } foreach (Island fallingIsland in _fallingIslands) { _grounded.Clear(); _queue.Clear(); foreach (PieceNode member in fallingIsland.Members) { if (member.Alive && member.Grounded && !_fallingSet.Contains(member) && _grounded.Add(member)) { _queue.Enqueue(member); } } while (_queue.Count > 0) { foreach (Contact contact in _queue.Dequeue().Contacts) { if (contact.Other.Alive && contact.Other.Island == fallingIsland && !_fallingSet.Contains(contact.Other) && _grounded.Add(contact.Other)) { _queue.Enqueue(contact.Other); } } } foreach (PieceNode member2 in fallingIsland.Members) { if (member2.Alive && !_grounded.Contains(member2) && !_fallingSet.Contains(member2) && Owned(member2.Wnt) && CanFall(member2.Wnt, worldAllowsFalls)) { _fallingSet.Add(member2); _falling.Add(member2); } } } for (int i = 0; i < _falling.Count; i++) { PieceNode pieceNode = _falling[i]; if (pieceNode.Attachment) { continue; } foreach (PieceNode item2 in pieceNode.Attached) { if (item2.Alive && !_fallingSet.Contains(item2) && item2.ContactsValid && !item2.OnGround && !HeldByOther(item2) && Owned(item2.Wnt) && CanFall(item2.Wnt, worldAllowsFalls)) { _fallingSet.Add(item2); _falling.Add(item2); } } } } private bool HeldByOther(PieceNode attachment) { foreach (Contact contact in attachment.Contacts) { if (contact.Other.Standing && !_fallingSet.Contains(contact.Other)) { return true; } } return false; } private static bool AttachmentReady(PieceNode node) { if (!Readiness.Settled(node.Wnt)) { return false; } if (node.ScanTrusted) { return true; } StructureManager.Instance.InvalidateContacts(node); return false; } private static bool Owned(WearNTear wnt) { if ((Object)(object)wnt != (Object)null && (Object)(object)wnt.m_nview != (Object)null && wnt.m_nview.IsValid()) { return wnt.m_nview.IsOwner(); } return false; } private static void SetCrack(ZDO zdo, PieceNode node, long ticks) { zdo.Set(CrackKey, ticks); if (node != null) { node.CrackTicks = ticks; } } private bool IsReady(Island island) { if (!_ready.TryGetValue(island, out var value)) { value = (_ready[island] = ComputeReady(island)); } return value; } private bool ComputeReady(Island island) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = default(Bounds); bool flag = true; foreach (PieceNode member in island.Members) { if (!member.Alive || !Readiness.Settled(member.Wnt)) { return false; } Vector3 position = ((Component)member.Wnt).transform.position; if (flag) { bounds = new Bounds(position, Vector3.zero); flag = false; } else { ((Bounds)(ref bounds)).Encapsulate(position); } } if (!Readiness.TerrainReady(bounds)) { return false; } bool result = true; foreach (PieceNode member2 in island.Members) { if (!member2.ScanTrusted) { StructureManager.Instance.InvalidateContacts(member2); result = false; } } return result; } } internal sealed class CrackEffects { private sealed class Visual { public readonly List<Transform> Shakers = new List<Transform>(); public readonly List<Vector3> Rest = new List<Vector3>(); public float Phase; public float NextEffect; } private const float ScanInterval = 0.25f; private const float EffectRadius = 80f; private const float EffectInterval = 1.3f; private const float ShakeAmplitude = 0.02f; private static readonly Color Bright = new Color(1f, 0.12f, 0.05f); private static readonly Color Dark = new Color(0.4f, 0.02f, 0.02f); private readonly Dictionary<WearNTear, Visual> _active = new Dictionary<WearNTear, Visual>(); private readonly List<WearNTear> _ended = new List<WearNTear>(); private readonly Dictionary<string, GameObject> _sounds = new Dictionary<string, GameObject>(); private float _nextScan; public static CrackEffects Instance { get; } = new CrackEffects(); public bool IsCracking(WearNTear wnt) { return _active.ContainsKey(wnt); } public void Tick() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { _active.Clear(); return; } if (Time.time >= _nextScan) { _nextScan = Time.time + 0.25f; Scan(((Component)localPlayer).transform.position); } foreach (KeyValuePair<WearNTear, Visual> item in _active) { if ((Object)(object)item.Key != (Object)null) { Animate(item.Key, item.Value); } } } private void Scan(Vector3 center) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) float num = 6400f; StructureManager instance = StructureManager.Instance; foreach (WearNTear allInstance in WearNTear.GetAllInstances()) { if (!((Object)(object)allInstance == (Object)null) && !((Object)(object)allInstance.m_nview == (Object)null) && allInstance.m_nview.IsValid()) { long num2 = allInstance.m_nview.GetZDO().GetLong(CollapseController.CrackKey, 0L); if (instance.TryGetNode(allInstance, out var node)) { node.CrackTicks = num2; } int num3; if (num2 != 0L) { Vector3 val = ((Component)allInstance).transform.position - center; num3 = ((((Vector3)(ref val)).sqrMagnitude <= num) ? 1 : 0); } else { num3 = 0; } bool flag = (byte)num3 != 0; if (flag && !_active.ContainsKey(allInstance)) { Begin(allInstance); } else if (!flag && _active.ContainsKey(allInstance)) { _ended.Add(allInstance); } } } foreach (WearNTear key in _active.Keys) { if ((Object)(object)key == (Object)null) { _ended.Add(key); } } foreach (WearNTear item in _ended) { End(item); } _ended.Clear(); } private void Begin(WearNTear wnt) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) Visual visual = new Visual { Phase = Random.value * 10f, NextEffect = Time.time }; Renderer[] componentsInChildren = ((Component)wnt).GetComponentsInChildren<Renderer>(); for (int i = 0; i < componentsInChildren.Length; i++) { Transform transform = ((Component)componentsInChildren[i]).transform; if (!((Object)(object)transform == (Object)(object)((Component)wnt).transform) && !visual.Shakers.Contains(transform) && !((Object)(object)((Component)transform).GetComponentInChildren<Collider>(true) != (Object)null) && !HasAncestorIn(transform, visual.Shakers, ((Component)wnt).transform)) { visual.Shakers.Add(transform); visual.Rest.Add(transform.localPosition); } } _active[wnt] = visual; } private void End(WearNTear wnt) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (!_active.TryGetValue(wnt, out var value)) { return; } _active.Remove(wnt); if ((Object)(object)wnt == (Object)null) { return; } for (int i = 0; i < value.Shakers.Count; i++) { if ((Object)(object)value.Shakers[i] != (Object)null) { value.Shakers[i].localPosition = value.Rest[i]; } } if (XRayView.Instance.Holds(wnt)) { XRayView.Instance.Reapply(wnt); } else { StressDisplay.Reset(wnt); } } private void Animate(WearNTear wnt, Visual v) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) float num = Time.time + v.Phase; float num2 = 0.5f + 0.5f * Mathf.Sin(num * 9f); StressDisplay.Apply(wnt, Color.Lerp(Dark, Bright, num2)); Vector3 val = new Vector3(Mathf.Sin(num * 53f), 0.5f * Mathf.Sin(num * 61f + 1.3f), Mathf.Sin(num * 47f + 2.1f)) * 0.02f; for (int i = 0; i < v.Shakers.Count; i++) { if ((Object)(object)v.Shakers[i] != (Object)null) { v.Shakers[i].localPosition = v.Rest[i] + val; } } if (!(Time.time < v.NextEffect)) { v.NextEffect = Time.time + 1.3f * Random.Range(0.8f, 1.2f); Vector3 position = ((Component)wnt).transform.position; wnt.m_hitEffect.Create(position, ((Component)wnt).transform.rotation, ((Component)wnt).transform, 1f, -1, default(ZDOID)); GameObject val2 = Sound(IsStone(wnt.m_materialType) ? "sfx_rock_wall_crumble" : "sfx_wood_break"); if ((Object)(object)val2 != (Object)null) { Object.Instantiate<GameObject>(val2, position, Quaternion.identity); } } } private GameObject Sound(string name) { if (!_sounds.TryGetValue(name, out var value)) { value = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(name) : null); _sounds[name] = value; if ((Object)(object)value == (Object)null) { Plugin.Log.LogWarning((object)("Sound '" + name + "' not found; cracking pieces will only use their hit effect.")); } } return value; } private static bool IsStone(MaterialType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if ((int)type != 1 && (int)type != 4 && (int)type != 5) { return (int)type == 7; } return true; } private static bool HasAncestorIn(Transform t, List<Transform> set, Transform root) { Transform parent = t.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root) { if (set.Contains(parent)) { return true; } parent = parent.parent; } return false; } } internal sealed class FallingDebris { private const string RpcName = "Fysik_Fall"; private const float ExpectSeconds = 10f; private const int MaxDebris = 400; private const float ColliderShrink = 0.8f; private static int s_effectLayer = -1; private static int s_restingMask; private static readonly Collider[] s_hits = (Collider[])(object)new Collider[64]; private static readonly HashSet<Renderer> s_lowLods = new HashSet<Renderer>(); private static readonly List<Collider> s_colliders = new List<Collider>(); private readonly Dictionary<ZDOID, float> _expected = new Dictionary<ZDOID, float>(); private readonly List<ZDOID> _expired = new List<ZDOID>(); private int _live; private float _effectWindow; private int _effectsInWindow; public static FallingDebris Instance { get; } = new FallingDebris(); public void RegisterRpc() { ZRoutedRpc.instance.Register<ZPackage>("Fysik_Fall", (Action<long, ZPackage>)OnFallRpc); } public static void Announce(List<PieceNode> falling) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance == null || falling.Count == 0) { return; } ZPackage val = new ZPackage(); val.Write(falling.Count); foreach (PieceNode item in falling) { val.Write(item.Id); } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Fysik_Fall", new object[1] { val }); } private void OnFallRpc(long sender, ZPackage package) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) int num = package.ReadInt(); float value = Time.time + 10f; for (int i = 0; i < num; i++) { _expected[package.ReadZDOID()] = value; } } public void Tick() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_0084: Unknown result type (might be due to invalid IL or missing references) if (_expected.Count == 0) { return; } _expired.Clear(); foreach (KeyValuePair<ZDOID, float> item in _expected) { if (Time.time > item.Value) { _expired.Add(item.Key); } } foreach (ZDOID item2 in _expired) { _expected.Remove(item2); } } public bool TryTakeOver(WearNTear wnt) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!FysikConfig.CollapseAnimation.Value || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsDedicated()) { return false; } ZDO val = (((Object)(object)wnt.m_nview != (Object)null) ? wnt.m_nview.GetZDO() : null); if (val == null || !_expected.Remove(val.m_uid) || _live >= 400) { return false; } return Spawn(wnt); } private bool Spawn(WearNTear wnt) { //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_0055: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0117: 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_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_014a: 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) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) if (s_effectLayer < 0) { s_effectLayer = LayerMask.NameToLayer("effect"); s_restingMask = LayerMask.GetMask(new string[3] { "piece", "Default", "Default_small" }); } GameObject val = new GameObject("fysik_debris") { layer = s_effectLayer }; val.transform.SetPositionAndRotation(((Component)wnt).transform.position, ((Component)wnt).transform.rotation); CollectLowLods(wnt); int num = 0; MeshRenderer[] componentsInChildren = ((Component)wnt).GetComponentsInChildren<MeshRenderer>(); foreach (MeshRenderer val2 in componentsInChildren) { if (((Renderer)val2).enabled && ((Component)val2).gameObject.activeInHierarchy && !s_lowLods.Contains((Renderer)(object)val2)) { MeshFilter component = ((Component)val2).GetComponent<MeshFilter>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.sharedMesh == (Object)null)) { GameObject val3 = new GameObject("mesh") { layer = s_effectLayer }; val3.transform.SetPositionAndRotation(((Component)val2).transform.position, ((Component)val2).transform.rotation); val3.transform.localScale = ((Component)val2).transform.lossyScale; val3.AddComponent<MeshFilter>().sharedMesh = component.sharedMesh; ((Renderer)val3.AddComponent<MeshRenderer>()).sharedMaterials = ((Renderer)val2).sharedMaterials; val3.transform.SetParent(val.transform, true); num++; } } } if (num == 0) { Object.Destroy((Object)(object)val); return false; } Renderer[] componentsInChildren2 = ((Component)wnt).GetComponentsInChildren<Renderer>(); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].enabled = false; } PieceNode node; PieceGeometry obj = (StructureManager.Instance.TryGetNode(wnt, out node) ? node.Geometry : PieceGeometry.Of(wnt)); s_colliders.Clear(); foreach (Obb box in obj.Boxes) { GameObject val4 = new GameObject("box") { layer = s_effectLayer }; val4.transform.SetPositionAndRotation(box.Center, box.Rotation); BoxCollider val5 = val4.AddComponent<BoxCollider>(); val5.size = box.Size * 0.8f; val4.transform.SetParent(val.transform, true); s_colliders.Add((Collider)(object)val5); int num2 = Physics.OverlapBoxNonAlloc(box.Center, box.Size * 0.5f + Vector3.one * 0.2f, s_hits, box.Rotation, s_restingMask); for (int j = 0; j < num2; j++) { Physics.IgnoreCollision((Collider)(object)val5, s_hits[j]); } } if (s_colliders.Count == 0) { Object.Destroy((Object)(object)val); return false; } Rigidbody obj2 = val.AddComponent<Rigidbody>(); obj2.mass = Mathf.Clamp((node != null && node.Mass > 0.0) ? ((float)node.Mass) : 50f, 5f, 5000f); obj2.interpolation = (RigidbodyInterpolation)1; obj2.isKinematic = true; val.AddComponent<Debris>().Init(wnt.m_destroyedEffect, Random.Range(0f, 0.08f)); _live++; return true; } private static void CollectLowLods(WearNTear wnt) { s_lowLods.Clear(); LODGroup[] componentsInChildren = ((Component)wnt).GetComponentsInChildren<LODGroup>(); for (int i = 0; i < componentsInChildren.Length; i++) { LOD[] lODs = componentsInChildren[i].GetLODs(); for (int j = 1; j < lODs.Length; j++) { Renderer[] renderers = lODs[j].renderers; foreach (Renderer val in renderers) { if ((Object)(object)val != (Object)null) { s_lowLods.Add(val); } } } } } public void OnDebrisGone() { _live = Mathf.Max(0, _live - 1); } public void PlayDestroyed(EffectList effect, Vector3 position, Quaternion rotation) { //IL_003e: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (effect != null) { if (Time.time > _effectWindow) { _effectWindow = Time.time + 0.5f; _effectsInWindow = 0; } if (_effectsInWindow++ < 6) { effect.Create(position, rotation, (Transform)null, 1f, -1, default(ZDOID)); } } } } internal sealed class Debris : MonoBehaviour { private const float ImpactSpeed = 2.5f; private const float MaxFallSeconds = 8f; private EffectList _destroyed; private float _release; private bool _released; private bool _done; private Rigidbody _body; public void Init(EffectList destroyed, float delay) { _destroyed = destroyed; _release = Time.time + delay; _body = ((Component)this).GetComponent<Rigidbody>(); } private void Update() { //IL_0038: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if (_done) { return; } if (!_released) { if (!(Time.time < _release)) { _released = true; _body.isKinematic = false; _body.linearVelocity = Vector3.down * 0.5f; _body.angularVelocity = Random.insideUnitSphere * 0.3f; } return; } float num = Time.time - _release; if (!(num > 8f)) { if (!(num > 0.6f)) { return; } Vector3 linearVelocity = _body.linearVelocity; if (!(((Vector3)(ref linearVelocity)).sqrMagnitude < 0.04f)) { return; } } Shatter(); } private void OnCollisionEnter(Collision collision) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (_released && !_done && Time.time - _release > 0.1f) { Vector3 relativeVelocity = collision.relativeVelocity; if (((Vector3)(ref relativeVelocity)).magnitude > 2.5f) { Shatter(); } } } private void Shatter() { //IL_0018: 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) _done = true; FallingDebris.Instance.PlayDestroyed(_destroyed, ((Component)this).transform.position, ((Component)this).transform.rotation); Destructible.CreateFragments(((Component)this).gameObject, false); Object.Destroy((Object)(object)((Component)this).gameObject); } private void OnDestroy() { FallingDebris.Instance.OnDebrisGone(); } } internal sealed class FysikCommand : ConsoleCommand { public override string Name => "fysik"; public override string Help => "stats | recalc | dump (dump writes the structure under the hammer to BepInEx/fysik-dump.txt)"; public override void Run(string[] args) { switch ((args.Length != 0) ? args[0].ToLowerInvariant() : "stats") { case "stats": Print(string.Format("Fysik {0}: {1} pieces cached, ", "0.1.1", StructureManager.Instance.NodeCount) + string.Format("{0} pending, X-ray {1}", StructureManager.Instance.PendingRequests, XRayView.Instance.Active ? "on" : "off")); Print("Last solve: " + StructureManager.Instance.LastSolve); Print(Ownership()); break; case "recalc": MaterialTable.Invalidate(); StructureManager.Instance.InvalidateAllResults(); Print("Fysik: every structure will be solved again."); break; case "dump": Dump(); break; default: Print("Usage: fysik " + ((ConsoleCommand)this).Help); break; } } private static string Ownership() { //IL_00a6: 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) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return "Ownership: no local player."; } long sessionID = ZDOMan.GetSessionID(); long num = (ZNet.instance.IsServer() ? null : ZNet.instance.GetServerPeer())?.m_uid ?? sessionID; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; foreach (WearNTear allInstance in WearNTear.GetAllInstances()) { if ((Object)(object)allInstance == (Object)null || (Object)(object)allInstance.m_nview == (Object)null || !allInstance.m_nview.IsValid()) { continue; } Vector3 val = ((Component)allInstance).transform.position - ((Component)localPlayer).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 4096f)) { long owner = allInstance.m_nview.GetZDO().GetOwner(); if (owner == sessionID) { num2++; } else if (owner == 0L) { num5++; } else if (owner == num) { num3++; } else { num4++; } } } return $"Building pieces within 64 m: {num2} yours (you calculate them), {num4} other players', " + $"{num3} the server's, {num5} without owner."; } internal static void Dump() { //IL_0163: 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_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) Piece val = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetHoveringPiece() : null); WearNTear wnt = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<WearNTear>() : null); PieceNode node = StructureManager.Instance.GetNode(wnt); if (node?.Island == null) { Print("Fysik: aim at a solved piece with the hammer first."); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, "Fysik: aim at a piece with the hammer", 0, (Sprite)null, false); } return; } CultureInfo invariantCulture = CultureInfo.InvariantCulture; StringBuilder stringBuilder = new StringBuilder(); Island island = node.Island; stringBuilder.AppendLine(string.Format("Fysik {0} dump, {1:yyyy-MM-dd HH:mm:ss}", "0.1.1", DateTime.Now)); stringBuilder.AppendLine(string.Format("Island: {0} pieces, solver {1}{2}", island.Members.Count, island.Solver, island.Stale ? " (stale)" : "")); stringBuilder.AppendLine("Last solve: " + StructureManager.Instance.LastSolve); stringBuilder.AppendLine(); stringBuilder.AppendLine("# index; prefab; zdo; material; mass kg; center; size; rotation (euler, degrees); ground points; mode; utilization; axial N; moment N·m; torque N·m"); for (int i = 0; i < island.Members.Count; i++) { PieceNode pieceNode = island.Members[i]; Obb main = pieceNode.Geometry.Main; stringBuilder.AppendLine(string.Format(invariantCulture, "{0}; {1}; {2}; {3}; {4:0.0}; {5}; {6}; {7}; {8}; {9}; {10:0.000}; {11:0}; {12:0}; {13:0}", i, pieceNode.Name, pieceNode.Id, pieceNode.MaterialName, pieceNode.Mass, Format(main.Center), Format(main.Size), Format(((Quaternion)(ref main.Rotation)).eulerAngles), pieceNode.GroundPoints.Count, pieceNode.Result.Mode, pieceNode.Result.Utilization, pieceNode.Result.AxialForce, pieceNode.Result.BendingMoment, pieceNode.Result.Torque)); } stringBuilder.AppendLine(); stringBuilder.AppendLine("# joints: index; index; point"); for (int j = 0; j < island.Members.Count; j++) { foreach (Contact contact in island.Members[j].Contacts) { int num = island.Members.IndexOf(contact.Other); if (num > j) { stringBuilder.AppendLine($"{j}; {num}; {Format(contact.Point)}"); } } } string text = Path.Combine(Paths.BepInExRootPath, "fysik-dump.txt"); File.WriteAllText(text, stringBuilder.ToString()); Print($"Fysik: {island.Members.Count} pieces written to {text}"); Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)1, $"Fysik: {island.Members.Count} pieces saved to fysik-dump.txt", 0, (Sprite)null, false); } } private static string Format(Vector3 v) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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) return string.Format(CultureInfo.InvariantCulture, "({0:0.###} {1:0.###} {2:0.###})", v.x, v.y, v.z); } private static void Print(string text) { Console instance = Console.instance; if (instance != null) { instance.Print(text); } Plugin.Log.LogInfo((object)text); } } internal static class Guard { private static readonly HashSet<string> s_reported = new HashSet<string>(); public static void Report(string site, Exception e) { if (s_reported.Add(site)) { Plugin.Log.LogError((object)$"{site} failed; vanilla behaviour continues. Further errors here are not logged.\n{e}"); } } } internal static class MaterialTable { private static readonly Dictionary<int, MaterialProps> s_cache = new Dictionary<int, MaterialProps>(); private static readonly HashSet<int> s_warned = new HashSet<int>(); internal static void Invalidate() { s_cache.Clear(); } internal static MaterialProps For(WearNTear wnt) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected I4, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) int num = (int)wnt.m_materialType; if (s_cache.TryGetValue(num, out var value)) { return value; } if (!FysikConfig.Materials.TryGetValue(wnt.m_materialType, out var value2)) { value2 = FysikConfig.Unknown; if (s_warned.Add(num)) { Plugin.Log.LogWarning((object)($"Unknown material type {num} on '{((Object)wnt).name}' (probably from another mod); " + "using the Material.Unknown settings.")); } } Stiffness(wnt.m_materialType, out var elasticity, out var shearRatio); value = new MaterialProps { Name = value2.Name, Density = value2.Density.Value, Elasticity = elasticity, ShearModulus = elasticity * shearRatio, CompressiveStrength = (double)value2.Compression.Value * 1000000.0 * FysikConfig.StrengthMultiplier, TensileStrength = (double)value2.Tension.Value * 1000000.0 * FysikConfig.StrengthMultiplier, ShearStrength = (double)value2.Shear.Value * 1000000.0 * FysikConfig.StrengthMultiplier, BucklingSlenderness = value2.Slenderness.Value }; s_cache[num] = value; return value; } private static void Stiffness(MaterialType type, out double elasticity, out double shearRatio) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown switch ((int)type) { case 0: elasticity = 10000000000.0; shearRatio = 0.0625; break; case 3: case 8: elasticity = 12000000000.0; shearRatio = 0.0625; break; case 6: elasticity = 15000000000.0; shearRatio = 0.0625; break; case 1: case 5: elasticity = 30000000000.0; shearRatio = 0.4; break; case 4: elasticity = 50000000000.0; shearRatio = 0.4; break; case 2: elasticity = 100000000000.0; shearRatio = 0.38; break; case 7: elasticity = 9000000000.0; shearRatio = 0.38; break; default: elasticity = 10000000000.0; shearRatio = 0.0625; break; } } } internal static class OwnershipHandover { private const float Interval = 2f; private static float s_next; private static readonly List<ZDO> s_zdos = new List<ZDO>(); private static readonly Dictionary<int, bool> s_isPiece = new Dictionary<int, bool>(); public static void Tick() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (Time.time < s_next) { return; } s_next = Time.time + 2f; ZNet instance = ZNet.instance; ZDOMan instance2 = ZDOMan.instance; ZNetScene instance3 = ZNetScene.instance; if ((Object)(object)instance == (Object)null || instance2 == null || (Object)(object)instance3 == (Object)null || !instance.IsDedicated()) { return; } List<ZNetPeer> peers = instance.GetPeers(); if (peers.Count == 0) { return; } long sessionID = ZDOMan.GetSessionID(); Vector2s zone = ZoneSystem.GetZone(instance.GetReferencePosition()); SimulationDistance syncedSimulationDistance = instance.GetSyncedSimulationDistance(); s_zdos.Clear(); instance2.FindSectorObjects(zone, new SimulationDistance(((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance, 0, ((SimulationDistance)(ref syncedSimulationDistance)).IsClassic), s_zdos, (List<ZDO>)null); int num = 0; foreach (ZDO s_zdo in s_zdos) { if (s_zdo.GetOwner() != sessionID || !s_zdo.Persistent || !IsBuildingPiece(instance3, s_zdo.GetPrefab())) { continue; } Vector3 position = s_zdo.GetPosition(); long num2 = 0L; float num3 = float.MaxValue; foreach (ZNetPeer item in peers) { if (item.IsReady() && instance2.IsInPeerActiveArea(position, item.m_uid)) { Vector3 val = item.m_refPos - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num3) { num3 = sqrMagnitude; num2 = item.m_uid; } } } if (num2 != 0L) { s_zdo.SetOwner(num2); num++; } } if (num > 0) { Plugin.Log.LogInfo((object)$"Handed {num} building pieces near the world centre to the nearest players."); } } private static bool IsBuildingPiece(ZNetScene scene, int prefab) { if (!s_isPiece.TryGetValue(prefab, out var value)) { GameObject prefab2 = scene.GetPrefab(prefab); value = (s_isPiece[prefab] = (Object)(object)prefab2 != (Object)null && (Object)(object)prefab2.GetComponent<WearNTear>() != (Object)null); } return value; } } internal static class Patches { [HarmonyPatch(typeof(WearNTear), "Awake")] private static class PieceAwake { private static void Postfix(WearNTear __instance) { try { Hooks.PieceAppeared(__instance); } catch (Exception e) { Guard.Report("Piece tracking (placed)", e); } } } [HarmonyPatch(typeof(WearNTear), "OnDestroy")] private static class PieceDestroyed { private static void Prefix(WearNTear __instance) { try { StructureManager.Instance.OnPieceDestroyed(__instance); } catch (Exception e) { Guard.Report("Piece tracking (removed)", e); } } } [HarmonyPatch(typeof(WearNTear), "RPC_HealthChanged")] private static class HealthChanged { private static void Postfix(WearNTear __instance) { try { StructureManager.Instance.OnPieceHealthChanged(__instance); } catch (Exception e) { Guard.Report("Piece tracking (health)", e); } } } [HarmonyPatch(typeof(WearNTear), "Highlight")] private static class Highlight { private static bool Prefix(WearNTear __instance) { try { return !Hooks.Highlight(__instance); } catch (Exception e) { Guard.Report("Hammer highlight", e); return true; } } } [HarmonyPatch(typeof(WearNTear), "ResetHighlight")] private static class ResetHighlight { private static bool Prefix(WearNTear __instance) { try { return !Hooks.KeepXRayColor(__instance); } catch (Exception e) { Guard.Report("X-ray highlight reset", e); return true; } } } [HarmonyPatch(typeof(WearNTear), "HaveSupport")] private static class HaveSupport { private static bool Prefix(WearNTear __instance, ref bool __result) { try { if (Hooks.VanillaSupportDecides(__instance)) { return true; } __result = true; return false; } catch (Exception e) { Guard.Report("Vanilla support switch", e); return true; } } } [HarmonyPatch(typeof(WearNTear), "RPC_CreateFragments")] private static class CreateFragments { private static bool Prefix(WearNTear __instance) { try { return !FallingDebris.Instance.TryTakeOver(__instance); } catch (Exception e) { Guard.Report("Collapse animation", e); return true; } } } [HarmonyPatch(typeof(ZNet), "Awake")] private static class RegisterRpcs { private static void Postfix() { try { FallingDebris.Instance.RegisterRpc(); } catch (Exception e) { Guard.Report("RPC registration", e); } } } [HarmonyPatch(typeof(Player), "UpdatePlacementGhost")] private static class PlacementGhost { private static void Postfix(Player __instance) { try { Hooks.PlacementGhost(__instance); } catch (Exception e) { Guard.Report("Placement preview", e); } } } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] private static class Crosshair { private static void Postfix(Hud __instance, Player player) { try { Hooks.Crosshair(__instance, player); } catch (Exception e) { Guard.Report("Crosshair stress text", e); } } } } internal static class Hooks { public static void PieceAppeared(WearNTear wnt) { if ((Object)(object)wnt.m_nview != (Object)null && wnt.m_nview.GetZDO() != null) { StructureManager.Instance.OnPieceAppeared(wnt); } } public static bool Highlight(WearNTear wnt) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!FysikConfig.HammerInfo.Value) { return false; } PieceNode pieceNode = StructureManager.Instance.Request(wnt); if (pieceNode == null || !pieceNode.Structural) { return false; } StressDisplay.Apply(wnt, StressDisplay.For(pieceNode)); ((MonoBehaviour)wnt).CancelInvoke("ResetHighlight"); ((MonoBehaviour)wnt).Invoke("ResetHighlight", 0.2f); return true; } public static bool KeepXRayColor(WearNTear wnt) { if (!XRayView.Instance.Holds(wnt)) { return false; } XRayView.Instance.Reapply(wnt); return true; } public static bool VanillaSupportDecides(WearNTear wnt) { if (FysikConfig.Mode.Value != StructureMode.DisplayOnly) { if (FysikConfig.Mode.Value == StructureMode.Physics) { return PieceNode.KeepsVanilla(wnt); } return false; } return true; } public static void PlacementGhost(Player player) { //IL_002d: 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) StructureManager instance = StructureManager.Instance; WearNTear val = ActiveGhost(player); if ((Object)(object)val == (Object)null) { instance.ClearPreview(); return; } instance.RequestPreview(val); if (instance.Preview != null && (int)player.m_placementStatus == 0) { StressDisplay.Apply(val, StressDisplay.ForPreview(instance.Preview)); } } public static void Crosshair(Hud hud, Player player) { string text = null; StructureManager instance = StructureManager.Instance; if ((Object)(object)ActiveGhost(player) != (Object)null) { text = StressDisplay.PreviewText(instance.Preview, instance.PreviewPending); } Piece val = (FysikConfig.HammerInfo.Value ? player.GetHoveringPiece() : null); WearNTear val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<WearNTear>() : null); if ((Object)(object)val2 != (Object)null) { string text2 = StressDisplay.HoverText(instance.Request(val2)); if (!string.IsNullOrEmpty(text2)) { text = (string.IsNullOrEmpty(text) ? text2 : (text + "\n" + text2)); } } if (!string.IsNullOrEmpty(text)) { string text3 = ((TMP_Text)hud.m_hoverName).text; ((TMP_Text)hud.m_hoverName).text = (string.IsNullOrEmpty(text3) ? text : (text3 + "\n" + text)); } } private static WearNTear ActiveGhost(Player player) { if (!FysikConfig.PlacementPreview.Value || !((Character)player).InPlaceMode()) { return null; } GameObject placementGhost = player.m_placementGhost; if (!((Object)(object)placementGhost != (Object)null) || !placementGhost.activeSelf) { return null; } return placementGhost.GetComponent<WearNTear>(); } } internal struct Obb { public Vector3 Center; public Quaternion Rotation; public Vector3 Size; public float Volume => Size.x * Size.y * Size.z; public Vector3 ClosestPoint(Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might