Decompiled source of SandevistanFX v1.5.0
Mods/SandevistanFX.dll
Decompiled a day 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.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BoneLib; using BoneLib.BoneMenu; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.AI; using Il2CppSLZ.Marrow.Interaction; using Il2CppSLZ.VRMK; using MelonLoader; using MelonLoader.Preferences; using SandevistanFX; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Main), "SandevistanFX", "1.5.0", "YourName", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: MelonAdditionalDependencies(new string[] { "BoneLib", "Quicksilver" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyFileVersion("1.5.0.0")] [assembly: AssemblyInformationalVersion("1.5.0")] [assembly: AssemblyTitle("SandevistanFX")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.5.0.0")] [module: UnverifiableCode] namespace SandevistanFX; internal sealed class AfterimageSystem { private sealed class PartMotion { private const float HeadTravel = 1f; private const float StillLimit = 0.1f; private Vector3 _refPart; private Vector3 _refHead; private bool _hasRef; private bool _reported; public bool Stationary; public bool Update(Vector3 part, Vector3 head, string name) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_0041: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (!_hasRef) { _refPart = part; _refHead = head; _hasRef = true; return Stationary; } if (FxMath.DistanceSq(head, _refHead) < 1f) { return Stationary; } Stationary = FxMath.DistanceSq(part, _refPart) < 0.010000001f; if (Stationary && !_reported) { _reported = true; Main.Log.Msg("Leaving '" + name + "' out of afterimages: it stays put while you move, so it isn't part of your body."); } _refPart = part; _refHead = head; return Stationary; } } private sealed class SkinnedProfile { public SkinnedMeshRenderer Source; public string Name; public int[] BoneMap; public int[] UniqueBones; public int RootIndex; public int SubMeshCount; public int BlendShapeCount; public IntPtr MeshKey; public IntPtr RootKey; public IntPtr[] BoneKeys; public bool ReportedFar; public readonly PartMotion Motion = new PartMotion(); } private sealed class StaticProfile { public MeshRenderer Source; public MeshFilter Filter; public string Name; public bool IsHead; public int SubMeshCount; public IntPtr MeshKey; public bool ReportedFar; public readonly PartMotion Motion = new PartMotion(); } private sealed class AvatarProfile { public int Id; public IntPtr AvatarKey; public Transform[] Bones; public int[] HeadBones; public float Reach; public int SkinnedComponentCount; public int StaticComponentCount; public readonly List<SkinnedProfile> Skinned = new List<SkinnedProfile>(); public readonly List<StaticProfile> Statics = new List<StaticProfile>(); } private sealed class AvatarGhost { public GameObject Root; public Transform[] Bones; public Vector3[] HeadScales; public SkinnedMeshRenderer[] Skinned; public bool[] SkinnedOn; public MeshRenderer[] Statics; public Transform[] StaticTransforms; public bool[] StaticOn; public Material Material; public int Generation; public int ProfileId; } private sealed class ItemPart { public GameObject Go; public Transform Transform; public MeshFilter Filter; public MeshRenderer Renderer; public int MaterialSlots = -1; public IntPtr MaterialKey = IntPtr.Zero; public Vector3 Position; public bool Shown; } private sealed class Snapshot { public float Born; public Color Color; public Vector3 Eye; public Vector3 LeftHand; public Vector3 RightHand; public bool HasLeftHand; public bool HasRightHand; public bool Revealed; public bool HeadHidden; public AvatarGhost Avatar; public readonly List<ItemPart> Items = new List<ItemPart>(); public Material ItemMaterial; public int ItemMaterialGeneration; } private sealed class ItemSources { public float RefreshAt; public readonly List<MeshRenderer> Renderers = new List<MeshRenderer>(); public readonly List<MeshFilter> Filters = new List<MeshFilter>(); } private struct PlayerPose { public Vector3 Eye; public Vector3 Body; public Vector3 LeftHand; public Vector3 RightHand; public bool HasLeftHand; public bool HasRightHand; public static PlayerPose Read(Camera camera) { //IL_0010: 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_001f: Unknown result type (might be due to invalid IL or missing references) //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) //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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) PlayerPose result = new PlayerPose { Eye = ((Component)camera).transform.position }; result.Body = BodyReference(result.Eye); Hand leftHand = Player.LeftHand; if (FxMath.Alive((Object)(object)leftHand)) { result.LeftHand = ((Component)leftHand).transform.position; result.HasLeftHand = true; } Hand rightHand = Player.RightHand; if (FxMath.Alive((Object)(object)rightHand)) { result.RightHand = ((Component)rightHand).transform.position; result.HasRightHand = true; } return result; } } private enum PoseResult { Ok, NothingToShow, Stale } private const int MaxItemPartsPerSnapshot = 48; private const float ItemEyeHideRadius = 0.3f; private const float AvatarSettleTime = 1f; private const float ValidateInterval = 0.5f; private const float HandRevealFactor = 1.75f; private GameObject _root; private AvatarProfile _profile; private int _nextProfileId; private IntPtr _failedAvatar = IntPtr.Zero; private float _profileRetryAt; private int _avatarGhostsCreated; private IntPtr _seenAvatar = IntPtr.Zero; private float _seenAvatarAt; private float _nextValidate; private bool _forceRebuild; private bool _validationOff; private readonly Queue<float> _rebuildTimes = new Queue<float>(); private Vector3[] _bonePositions = (Vector3[])(object)new Vector3[0]; private readonly Stack<AvatarGhost> _avatarPool = new Stack<AvatarGhost>(); private readonly Stack<ItemPart> _itemPool = new Stack<ItemPart>(); private readonly Stack<Snapshot> _snapshotPool = new Stack<Snapshot>(); private readonly List<Snapshot> _live = new List<Snapshot>(); private readonly Dictionary<IntPtr, ItemSources> _itemSources = new Dictionary<IntPtr, ItemSources>(); private readonly List<Vector3> _points = new List<Vector3>(); private readonly List<Vector3> _lastPoints = new List<Vector3>(); private float _lastSpawn = -999f; private static bool _meshInfoBroken; private static bool _blendShapesBroken; private static BlendKind GhostBlend { get { if (!Settings.AfterimageAdditive.Value) { return BlendKind.Alpha; } return BlendKind.Additive; } } private static Vector3 BodyReference(Vector3 fallback) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) Transform head = Player.Head; if (!FxMath.Alive((Object)(object)head)) { return fallback; } return head.position; } public void Tick(PlayerScanner scanner, Camera camera) { float now = FxClock.Now; if (!EnsureRoot()) { return; } if (!FxMath.Alive((Object)(object)camera)) { HideAll(); return; } EnsureProfile(now, immediate: true); PlayerPose pose = PlayerPose.Read(camera); if (_lastPoints.Count == 0) { GatherTrackedPoints(pose); _lastPoints.AddRange(_points); } else if (now - _lastSpawn >= Math.Max(0.01f, Settings.AfterimageInterval.Value)) { GatherTrackedPoints(pose); if (HasMoved()) { Spawn(scanner, now, pose); _lastSpawn = now; _lastPoints.Clear(); _lastPoints.AddRange(_points); } } UpdateLive(now, pose); } public void HideAll() { for (int i = 0; i < _live.Count; i++) { Recycle(_live[i]); } _live.Clear(); _lastPoints.Clear(); _lastSpawn = -999f; } public void Prewarm() { if (!Settings.AfterimagesEnabled.Value || !EnsureRoot()) { return; } EnsureProfile(FxClock.Now, immediate: false); if (_profile == null) { return; } int num = MaxAlive(); if (_avatarGhostsCreated < num) { AvatarGhost avatarGhost = CreateAvatarGhost(); if (avatarGhost != null) { _avatarPool.Push(avatarGhost); } } } public void OnAvatarChanged() { HideAll(); DropProfile(); _seenAvatar = IntPtr.Zero; } public void OnMaterialSettingsChanged() { HideAll(); DropProfile(); } public void DestroyAll() { HideAll(); DropProfile(); while (_snapshotPool.Count > 0) { MaterialFactory.Destroy(_snapshotPool.Pop().ItemMaterial); } _itemPool.Clear(); _itemSources.Clear(); _seenAvatar = IntPtr.Zero; _failedAvatar = IntPtr.Zero; if (FxMath.Alive((Object)(object)_root)) { Object.Destroy((Object)(object)_root); } _root = null; } private void Spawn(PlayerScanner scanner, float now, PlayerPose pose) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) int num = MaxAlive(); while (_live.Count >= num) { Recycle(_live[0]); _live.RemoveAt(0); } Snapshot snapshot = ((_snapshotPool.Count > 0) ? _snapshotPool.Pop() : new Snapshot()); snapshot.Born = now; snapshot.Revealed = false; snapshot.HeadHidden = false; snapshot.Eye = pose.Eye; snapshot.LeftHand = pose.LeftHand; snapshot.RightHand = pose.RightHand; snapshot.HasLeftHand = pose.HasLeftHand; snapshot.HasRightHand = pose.HasRightHand; float h = now * Settings.AfterimageHueSpeed.Value; snapshot.Color = FxMath.HsvToRgb(h, Settings.AfterimageSaturation.Value, 1f); try { if (_profile != null) { AvatarGhost avatarGhost = RentAvatarGhost(); if (avatarGhost != null) { PoseResult poseResult = PoseAvatarGhost(avatarGhost, pose.Body); if (poseResult == PoseResult.Ok) { snapshot.Avatar = avatarGhost; } else { ReturnAvatarGhost(avatarGhost); if (poseResult == PoseResult.Stale) { _forceRebuild = true; } } } } if (Settings.AfterimageIncludeItems.Value && scanner.ItemRoots.Count > 0) { SpawnItemParts(snapshot, scanner, now, pose.Body); } } catch { Recycle(snapshot); throw; } if (snapshot.Avatar == null && snapshot.Items.Count == 0) { _snapshotPool.Push(snapshot); return; } SetSnapshotAlpha(snapshot, 0f); _live.Add(snapshot); } private void UpdateLive(float now, PlayerPose pose) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_011d: 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_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: 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_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) float num = Math.Max(0.05f, Settings.AfterimageLifetime.Value); float num2 = Math.Min(Math.Max(0f, Settings.AfterimageRevealDelay.Value), num * 0.5f); float alpha = FxMath.Clamp01(Settings.AfterimageOpacity.Value); float num3 = Math.Max(0f, Settings.AfterimageHeadHideRadius.Value); float num4 = num3 * num3; float num5 = Math.Max(0f, Settings.AfterimageRevealDistance.Value); float num6 = num5 * num5; float num7 = num6 * 1.75f * 1.75f; float num8 = 0.09f; Vector3 eye = pose.Eye; for (int num9 = _live.Count - 1; num9 >= 0; num9--) { Snapshot snapshot = _live[num9]; float num10 = now - snapshot.Born; if (num10 >= num) { Recycle(snapshot); _live.RemoveAt(num9); continue; } bool flag = FxMath.DistanceSq(eye, snapshot.Eye) < num4; if (flag != snapshot.HeadHidden) { SetHeadHidden(snapshot, flag); } if (!snapshot.Revealed) { if (num10 < num2 || (!(FxMath.DistanceSq(eye, snapshot.Eye) >= num6) && (!pose.HasLeftHand || !snapshot.HasLeftHand || !(FxMath.DistanceSq(pose.LeftHand, snapshot.LeftHand) >= num7)) && (!pose.HasRightHand || !snapshot.HasRightHand || !(FxMath.DistanceSq(pose.RightHand, snapshot.RightHand) >= num7)))) { continue; } SetSnapshotAlpha(snapshot, alpha); if (snapshot.Avatar != null && FxMath.Alive((Object)(object)snapshot.Avatar.Root)) { snapshot.Avatar.Root.SetActive(true); } snapshot.Revealed = true; } for (int i = 0; i < snapshot.Items.Count; i++) { ItemPart itemPart = snapshot.Items[i]; bool flag2 = FxMath.DistanceSq(itemPart.Position, eye) >= num8; if (flag2 != itemPart.Shown && FxMath.Alive((Object)(object)itemPart.Go)) { itemPart.Go.SetActive(flag2); itemPart.Shown = flag2; } } } } private static int MaxAlive() { int value = Settings.AfterimageMaxCount.Value; if (value < 1) { return 1; } if (value <= 100) { return value; } return 100; } private void GatherTrackedPoints(PlayerPose pose) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) _points.Clear(); _points.Add(pose.Eye); if (pose.HasLeftHand) { _points.Add(pose.LeftHand); } if (pose.HasRightHand) { _points.Add(pose.RightHand); } } private bool HasMoved() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (_lastPoints.Count != _points.Count) { return true; } float num = Math.Max(0f, Settings.AfterimageMinMove.Value); float num2 = num * num; for (int i = 0; i < _points.Count; i++) { if (FxMath.DistanceSq(_points[i], _lastPoints[i]) >= num2) { return true; } } return false; } private void SetSnapshotAlpha(Snapshot snapshot, float alpha) { //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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Color color = snapshot.Color; color.a = alpha; if (snapshot.Avatar != null) { MaterialFactory.SetColor(snapshot.Avatar.Material, color); } if (snapshot.Items.Count > 0) { MaterialFactory.SetColor(snapshot.ItemMaterial, color); } } private void SetVisible(Snapshot snapshot, bool visible) { if (snapshot.Avatar != null && FxMath.Alive((Object)(object)snapshot.Avatar.Root)) { snapshot.Avatar.Root.SetActive(visible); } for (int i = 0; i < snapshot.Items.Count; i++) { ItemPart itemPart = snapshot.Items[i]; if (FxMath.Alive((Object)(object)itemPart.Go)) { itemPart.Go.SetActive(visible); } itemPart.Shown = visible; } } private void SetHeadHidden(Snapshot snapshot, bool hidden) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) snapshot.HeadHidden = hidden; AvatarGhost avatar = snapshot.Avatar; if (avatar == null || _profile == null) { return; } int[] headBones = _profile.HeadBones; for (int i = 0; i < headBones.Length; i++) { int num = headBones[i]; Transform val = avatar.Bones[num]; if (!((Object)(object)val == (Object)null)) { val.localScale = (hidden ? Vector3.zero : avatar.HeadScales[i]); } } for (int j = 0; j < avatar.Statics.Length; j++) { if (_profile.Statics[j].IsHead) { MeshRenderer val2 = avatar.Statics[j]; if ((Object)(object)val2 != (Object)null) { ((Renderer)val2).enabled = avatar.StaticOn[j] && !hidden; } } } } private void Recycle(Snapshot snapshot) { SetVisible(snapshot, visible: false); if (snapshot.Avatar != null) { AvatarGhost avatar = snapshot.Avatar; snapshot.Avatar = null; ReturnAvatarGhost(avatar); } for (int i = 0; i < snapshot.Items.Count; i++) { ItemPart itemPart = snapshot.Items[i]; if (FxMath.Alive((Object)(object)itemPart.Go)) { _itemPool.Push(itemPart); } } snapshot.Items.Clear(); snapshot.Revealed = false; snapshot.HeadHidden = false; _snapshotPool.Push(snapshot); } private bool Reusable(AvatarGhost ghost) { if (_profile != null && ghost.ProfileId == _profile.Id && FxMath.Alive((Object)(object)ghost.Root) && FxMath.Alive((Object)(object)ghost.Material)) { return ghost.Generation == MaterialFactory.Generation; } return false; } private AvatarGhost RentAvatarGhost() { while (_avatarPool.Count > 0) { AvatarGhost avatarGhost = _avatarPool.Pop(); if (Reusable(avatarGhost)) { return avatarGhost; } DestroyAvatarGhost(avatarGhost); } return CreateAvatarGhost(); } private void ReturnAvatarGhost(AvatarGhost ghost) { if (ghost != null) { if (FxMath.Alive((Object)(object)ghost.Root)) { ghost.Root.SetActive(false); } if (Reusable(ghost)) { _avatarPool.Push(ghost); } else { DestroyAvatarGhost(ghost); } } } private AvatarGhost CreateAvatarGhost() { //IL_002a: 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_0052: Expected O, but got Unknown //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Expected O, but got Unknown //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Expected O, but got Unknown AvatarProfile profile = _profile; if (profile == null) { return null; } Material val = MaterialFactory.Create("Afterimage", GhostBlend, new Color(1f, 1f, 1f, 0f), 4550, cullOff: false, alwaysOnTop: false); if ((Object)(object)val == (Object)null) { return null; } GameObject val2 = new GameObject("[SandevistanFX] Afterimage"); val2.SetActive(false); FxMath.AttachAtOrigin(val2.transform, _root.transform); AvatarGhost avatarGhost = new AvatarGhost { Root = val2, Material = val, Generation = MaterialFactory.Generation, ProfileId = profile.Id, Bones = (Transform[])(object)new Transform[profile.Bones.Length], HeadScales = (Vector3[])(object)new Vector3[profile.HeadBones.Length], Skinned = (SkinnedMeshRenderer[])(object)new SkinnedMeshRenderer[profile.Skinned.Count], SkinnedOn = new bool[profile.Skinned.Count], Statics = (MeshRenderer[])(object)new MeshRenderer[profile.Statics.Count], StaticTransforms = (Transform[])(object)new Transform[profile.Statics.Count], StaticOn = new bool[profile.Statics.Count] }; for (int i = 0; i < avatarGhost.Bones.Length; i++) { GameObject val3 = new GameObject(); FxMath.AttachAtOrigin(val3.transform, val2.transform); avatarGhost.Bones[i] = val3.transform; } for (int j = 0; j < profile.Skinned.Count; j++) { SkinnedProfile skinnedProfile = profile.Skinned[j]; GameObject val4 = new GameObject("Skin"); val4.layer = ((Component)skinnedProfile.Source).gameObject.layer; FxMath.AttachAtOrigin(val4.transform, val2.transform); Transform[] array = (Transform[])(object)new Transform[skinnedProfile.BoneMap.Length]; for (int k = 0; k < array.Length; k++) { array[k] = avatarGhost.Bones[skinnedProfile.BoneMap[k]]; } SkinnedMeshRenderer val5 = val4.AddComponent<SkinnedMeshRenderer>(); val5.sharedMesh = skinnedProfile.Source.sharedMesh; val5.bones = Il2CppReferenceArray<Transform>.op_Implicit(array); val5.rootBone = avatarGhost.Bones[skinnedProfile.RootIndex]; ((Renderer)val5).localBounds = new Bounds(new Vector3(0f, 0f, 0f), new Vector3(5f, 5f, 5f)); RendererUtil.DisableShadows((Renderer)(object)val5); ((Renderer)val5).sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit(RendererUtil.Repeat(val, skinnedProfile.SubMeshCount)); avatarGhost.Skinned[j] = val5; } for (int l = 0; l < profile.Statics.Count; l++) { StaticProfile staticProfile = profile.Statics[l]; GameObject val6 = new GameObject("Static"); val6.layer = ((Component)staticProfile.Source).gameObject.layer; FxMath.AttachAtOrigin(val6.transform, val2.transform); val6.AddComponent<MeshFilter>().sharedMesh = staticProfile.Filter.sharedMesh; MeshRenderer val7 = val6.AddComponent<MeshRenderer>(); RendererUtil.DisableShadows((Renderer)(object)val7); ((Renderer)val7).sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit(RendererUtil.Repeat(val, staticProfile.SubMeshCount)); avatarGhost.Statics[l] = val7; avatarGhost.StaticTransforms[l] = val6.transform; } _avatarGhostsCreated++; return avatarGhost; } private PoseResult PoseAvatarGhost(AvatarGhost ghost, Vector3 body) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0133: 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_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) AvatarProfile profile = _profile; Transform[] bones = profile.Bones; Transform[] bones2 = ghost.Bones; if (_bonePositions.Length < bones.Length) { _bonePositions = (Vector3[])(object)new Vector3[bones.Length]; } for (int i = 0; i < bones.Length; i++) { Transform val = bones[i]; if ((Object)(object)val == (Object)null) { return PoseResult.Stale; } Vector3 position = val.position; _bonePositions[i] = position; Transform obj = bones2[i]; obj.SetPositionAndRotation(position, val.rotation); obj.localScale = val.lossyScale; } int[] headBones = profile.HeadBones; for (int j = 0; j < headBones.Length; j++) { ghost.HeadScales[j] = bones2[headBones[j]].localScale; } float num = profile.Reach * profile.Reach; bool flag = false; for (int k = 0; k < profile.Skinned.Count; k++) { SkinnedProfile skinnedProfile = profile.Skinned[k]; SkinnedMeshRenderer val2 = ghost.Skinned[k]; bool flag2 = FxMath.Alive((Object)(object)skinnedProfile.Source) && ((Renderer)skinnedProfile.Source).enabled && ((Component)skinnedProfile.Source).gameObject.activeInHierarchy; if (flag2) { Vector3 val3 = Center(skinnedProfile.UniqueBones, _bonePositions); float num2 = FxMath.DistanceSq(val3, body); if (num2 > num) { flag2 = false; ReportFar(ref skinnedProfile.ReportedFar, skinnedProfile.Name, num2); } else if (skinnedProfile.Motion.Update(val3, body, skinnedProfile.Name)) { flag2 = false; } } ghost.SkinnedOn[k] = flag2; ((Renderer)val2).enabled = flag2; if (flag2) { flag = true; if (skinnedProfile.BlendShapeCount > 0) { CopyBlendShapes(skinnedProfile.Source, val2, skinnedProfile.BlendShapeCount); } } } for (int l = 0; l < profile.Statics.Count; l++) { StaticProfile staticProfile = profile.Statics[l]; bool flag3 = FxMath.Alive((Object)(object)staticProfile.Source) && ((Renderer)staticProfile.Source).enabled && ((Component)staticProfile.Source).gameObject.activeInHierarchy; Transform val4 = null; if (flag3) { val4 = ((Component)staticProfile.Source).transform; Vector3 position2 = val4.position; float num3 = FxMath.DistanceSq(position2, body); if (num3 > num) { flag3 = false; ReportFar(ref staticProfile.ReportedFar, staticProfile.Name, num3); } else if (staticProfile.Motion.Update(position2, body, staticProfile.Name)) { flag3 = false; } } ghost.StaticOn[l] = flag3; ((Renderer)ghost.Statics[l]).enabled = flag3; if (flag3) { flag = true; Transform obj2 = ghost.StaticTransforms[l]; obj2.SetPositionAndRotation(val4.position, val4.rotation); obj2.localScale = val4.lossyScale; } } if (!flag) { return PoseResult.NothingToShow; } return PoseResult.Ok; } private static Vector3 Center(int[] indices, Vector3[] positions) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) float num = 0f; float num2 = 0f; float num3 = 0f; for (int i = 0; i < indices.Length; i++) { Vector3 val = positions[indices[i]]; num += val.x; num2 += val.y; num3 += val.z; } float num4 = 1f / (float)Math.Max(1, indices.Length); return new Vector3(num * num4, num2 * num4, num3 * num4); } private static void ReportFar(ref bool reported, string name, float distSq) { if (!reported) { reported = true; Main.Log.Msg("Leaving '" + name + "' out of afterimages: it's " + Math.Sqrt(distSq).ToString("0.0") + " m from you, so it isn't following your body."); } } private void DestroyAvatarGhost(AvatarGhost ghost) { if (ghost != null) { if (FxMath.Alive((Object)(object)ghost.Root)) { Object.Destroy((Object)(object)ghost.Root); } MaterialFactory.Destroy(ghost.Material); } } private void SpawnItemParts(Snapshot snapshot, PlayerScanner scanner, float now, Vector3 body) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0204: 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_0212: Unknown result type (might be due to invalid IL or missing references) if (!FxMath.Alive((Object)(object)snapshot.ItemMaterial) || snapshot.ItemMaterialGeneration != MaterialFactory.Generation) { MaterialFactory.Destroy(snapshot.ItemMaterial); snapshot.ItemMaterial = MaterialFactory.Create("Afterimage Item", GhostBlend, new Color(1f, 1f, 1f, 0f), 4550, cullOff: false, alwaysOnTop: false); snapshot.ItemMaterialGeneration = MaterialFactory.Generation; } if ((Object)(object)snapshot.ItemMaterial == (Object)null) { return; } int num = 48; IntPtr pointer = ((Il2CppObjectBase)snapshot.ItemMaterial).Pointer; float num2 = ((_profile != null) ? _profile.Reach : 2.5f) + 1.5f; float num3 = num2 * num2; for (int i = 0; i < scanner.ItemRoots.Count; i++) { if (num <= 0) { break; } GameObject val = scanner.ItemRoots[i]; if (!FxMath.Alive((Object)(object)val)) { continue; } ItemSources itemSources = GetItemSources(val, now); for (int j = 0; j < itemSources.Renderers.Count; j++) { if (num <= 0) { break; } MeshRenderer val2 = itemSources.Renderers[j]; MeshFilter val3 = itemSources.Filters[j]; if (!FxMath.Alive((Object)(object)val2) || !FxMath.Alive((Object)(object)val3) || !((Renderer)val2).enabled || !((Component)val2).gameObject.activeInHierarchy) { continue; } Mesh sharedMesh = val3.sharedMesh; if ((Object)(object)sharedMesh == (Object)null) { continue; } Transform transform = ((Component)val2).transform; Vector3 position = transform.position; if (!(FxMath.DistanceSq(position, body) > num3)) { ItemPart itemPart = RentItemPart(); itemPart.Go.layer = ((Component)val2).gameObject.layer; itemPart.Filter.sharedMesh = sharedMesh; int num4 = SubMeshCount(sharedMesh); if (itemPart.MaterialSlots != num4 || itemPart.MaterialKey != pointer) { ((Renderer)itemPart.Renderer).sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit(RendererUtil.Repeat(snapshot.ItemMaterial, num4)); itemPart.MaterialSlots = num4; itemPart.MaterialKey = pointer; } itemPart.Transform.SetPositionAndRotation(position, transform.rotation); itemPart.Transform.localScale = transform.lossyScale; itemPart.Position = position; itemPart.Shown = false; snapshot.Items.Add(itemPart); num--; } } } } private ItemSources GetItemSources(GameObject root, float now) { IntPtr pointer = ((Il2CppObjectBase)root).Pointer; if (!_itemSources.TryGetValue(pointer, out var value)) { if (_itemSources.Count > 128) { _itemSources.Clear(); } value = new ItemSources(); _itemSources[pointer] = value; } if (now >= value.RefreshAt) { value.RefreshAt = now + 1f; value.Renderers.Clear(); value.Filters.Clear(); foreach (MeshRenderer componentsInChild in root.GetComponentsInChildren<MeshRenderer>(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { MeshFilter component = ((Component)componentsInChild).GetComponent<MeshFilter>(); if (!((Object)(object)component == (Object)null)) { value.Renderers.Add(componentsInChild); value.Filters.Add(component); } } } } return value; } private ItemPart RentItemPart() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown while (_itemPool.Count > 0) { ItemPart itemPart = _itemPool.Pop(); if (FxMath.Alive((Object)(object)itemPart.Go)) { return itemPart; } } GameObject val = new GameObject("[SandevistanFX] Afterimage Item"); val.SetActive(false); FxMath.AttachAtOrigin(val.transform, _root.transform); ItemPart obj = new ItemPart { Go = val, Transform = val.transform, Filter = val.AddComponent<MeshFilter>(), Renderer = val.AddComponent<MeshRenderer>() }; RendererUtil.DisableShadows((Renderer)(object)obj.Renderer); return obj; } private bool EnsureRoot() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown if (FxMath.Alive((Object)(object)_root)) { return true; } _live.Clear(); _avatarPool.Clear(); _itemPool.Clear(); _avatarGhostsCreated = 0; _root = new GameObject("[SandevistanFX] Afterimages"); Object.DontDestroyOnLoad((Object)(object)_root); return true; } private void EnsureProfile(float now, bool immediate) { RigManager rigManager = Player.RigManager; if (!FxMath.Alive((Object)(object)rigManager)) { return; } Avatar avatar = rigManager.avatar; if (!FxMath.Alive((Object)(object)avatar)) { return; } IntPtr pointer = ((Il2CppObjectBase)avatar).Pointer; if (pointer != _seenAvatar) { _seenAvatar = pointer; _seenAvatarAt = now; _rebuildTimes.Clear(); _validationOff = false; } if (_profile != null && _profile.AvatarKey == pointer) { if (_forceRebuild) { Rebuild(((Component)avatar).gameObject, pointer, now); } else if (now >= _nextValidate) { _nextValidate = now + 0.5f; if (!_validationOff && !ProfileMatches(((Component)avatar).gameObject)) { Main.Log.Msg("Avatar meshes or bones changed; refreshing afterimages."); Rebuild(((Component)avatar).gameObject, pointer, now); } else { TrackPartMotion(); } } } else if ((immediate || !(now - _seenAvatarAt < 1f)) && (!(pointer == _failedAvatar) || !(now < _profileRetryAt))) { Rebuild(((Component)avatar).gameObject, pointer, now); } } private void Rebuild(GameObject avatar, IntPtr key, float now) { HideAll(); DropProfile(); _rebuildTimes.Enqueue(now); while (_rebuildTimes.Count > 0 && now - _rebuildTimes.Peek() > 10f) { _rebuildTimes.Dequeue(); } if (_rebuildTimes.Count > 4 && !_validationOff) { _validationOff = true; Main.Log.Warning("This avatar keeps changing its meshes; afterimages will stop re-checking it until you switch avatars."); } _profile = BuildProfile(avatar, key, ++_nextProfileId); _nextValidate = now + 0.5f; if (_profile == null) { _failedAvatar = key; _profileRetryAt = now + 2f; } } private void TrackPartMotion() { //IL_0010: 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_0064: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) Transform head = Player.Head; if (!FxMath.Alive((Object)(object)head)) { return; } Vector3 position = head.position; AvatarProfile profile = _profile; if (_bonePositions.Length < profile.Bones.Length) { _bonePositions = (Vector3[])(object)new Vector3[profile.Bones.Length]; } for (int i = 0; i < profile.Bones.Length; i++) { Transform val = profile.Bones[i]; if ((Object)(object)val == (Object)null) { return; } _bonePositions[i] = val.position; } for (int j = 0; j < profile.Skinned.Count; j++) { SkinnedProfile skinnedProfile = profile.Skinned[j]; skinnedProfile.Motion.Update(Center(skinnedProfile.UniqueBones, _bonePositions), position, skinnedProfile.Name); } for (int k = 0; k < profile.Statics.Count; k++) { StaticProfile staticProfile = profile.Statics[k]; if (FxMath.Alive((Object)(object)staticProfile.Source)) { staticProfile.Motion.Update(((Component)staticProfile.Source).transform.position, position, staticProfile.Name); } } } private bool ProfileMatches(GameObject avatar) { AvatarProfile profile = _profile; for (int i = 0; i < profile.Skinned.Count; i++) { SkinnedProfile skinnedProfile = profile.Skinned[i]; if (!FxMath.Alive((Object)(object)skinnedProfile.Source)) { return false; } if (PointerOf((Object)(object)skinnedProfile.Source.sharedMesh) != skinnedProfile.MeshKey) { return false; } if (PointerOf((Object)(object)skinnedProfile.Source.rootBone) != skinnedProfile.RootKey) { return false; } Il2CppReferenceArray<Transform> bones = skinnedProfile.Source.bones; if (((Il2CppArrayBase<Transform>)(object)bones).Length != skinnedProfile.BoneKeys.Length) { return false; } for (int j = 0; j < skinnedProfile.BoneKeys.Length; j++) { if (PointerOf((Object)(object)((Il2CppArrayBase<Transform>)(object)bones)[j]) != skinnedProfile.BoneKeys[j]) { return false; } } } for (int k = 0; k < profile.Statics.Count; k++) { StaticProfile staticProfile = profile.Statics[k]; if (!FxMath.Alive((Object)(object)staticProfile.Source) || !FxMath.Alive((Object)(object)staticProfile.Filter)) { return false; } if (PointerOf((Object)(object)staticProfile.Filter.sharedMesh) != staticProfile.MeshKey) { return false; } } if (avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true).Length != profile.SkinnedComponentCount) { return false; } if (avatar.GetComponentsInChildren<MeshRenderer>(true).Length != profile.StaticComponentCount) { return false; } return true; } private static IntPtr PointerOf(Object obj) { if (!(obj == (Object)null)) { return ((Il2CppObjectBase)obj).Pointer; } return IntPtr.Zero; } private void DropProfile() { while (_avatarPool.Count > 0) { DestroyAvatarGhost(_avatarPool.Pop()); } _avatarGhostsCreated = 0; _profile = null; _forceRebuild = false; } private static AvatarProfile BuildProfile(GameObject avatar, IntPtr key, int id) { Transform val = FindHead(avatar); AvatarProfile avatarProfile = new AvatarProfile { AvatarKey = key, Id = id }; List<Transform> union = new List<Transform>(); Dictionary<IntPtr, int> unionIndex = new Dictionary<IntPtr, int>(); foreach (SkinnedMeshRenderer componentsInChild in avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true)) { avatarProfile.SkinnedComponentCount++; if ((Object)(object)componentsInChild == (Object)null) { continue; } Mesh sharedMesh = componentsInChild.sharedMesh; if ((Object)(object)sharedMesh == (Object)null) { continue; } Il2CppReferenceArray<Transform> bones = componentsInChild.bones; int length = ((Il2CppArrayBase<Transform>)(object)bones).Length; if (length == 0) { continue; } Transform rootBone = componentsInChild.rootBone; int num = IndexOf(((Object)(object)rootBone != (Object)null) ? rootBone : ((Component)componentsInChild).transform); int[] array = new int[length]; IntPtr[] array2 = new IntPtr[length]; HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < length; i++) { Transform val2 = ((Il2CppArrayBase<Transform>)(object)bones)[i]; array2[i] = PointerOf((Object)(object)val2); int num2 = IndexOf(val2); array[i] = ((num2 < 0) ? num : num2); if (num2 >= 0) { hashSet.Add(num2); } } if (hashSet.Count == 0) { hashSet.Add(num); } int[] array3 = new int[hashSet.Count]; hashSet.CopyTo(array3); avatarProfile.Skinned.Add(new SkinnedProfile { Source = componentsInChild, Name = ((Object)componentsInChild).name, BoneMap = array, UniqueBones = array3, RootIndex = num, SubMeshCount = SubMeshCount(sharedMesh), BlendShapeCount = BlendShapeCount(sharedMesh), MeshKey = ((Il2CppObjectBase)sharedMesh).Pointer, RootKey = PointerOf((Object)(object)rootBone), BoneKeys = array2 }); } foreach (MeshRenderer componentsInChild2 in avatar.GetComponentsInChildren<MeshRenderer>(true)) { avatarProfile.StaticComponentCount++; if ((Object)(object)componentsInChild2 == (Object)null) { continue; } MeshFilter component = ((Component)componentsInChild2).GetComponent<MeshFilter>(); if (!((Object)(object)component == (Object)null)) { Mesh sharedMesh2 = component.sharedMesh; if (!((Object)(object)sharedMesh2 == (Object)null)) { avatarProfile.Statics.Add(new StaticProfile { Source = componentsInChild2, Filter = component, Name = ((Object)componentsInChild2).name, IsHead = ((Object)(object)val != (Object)null && FxMath.IsDescendantOf(((Component)componentsInChild2).transform, val)), SubMeshCount = SubMeshCount(sharedMesh2), MeshKey = ((Il2CppObjectBase)sharedMesh2).Pointer }); } } } if (avatarProfile.Skinned.Count == 0 && avatarProfile.Statics.Count == 0) { return null; } avatarProfile.Bones = union.ToArray(); List<int> list = new List<int>(); if ((Object)(object)val != (Object)null) { for (int j = 0; j < avatarProfile.Bones.Length; j++) { if (FxMath.IsDescendantOf(avatarProfile.Bones[j], val)) { list.Add(j); } } } avatarProfile.HeadBones = list.ToArray(); float num3 = SkeletonSize(avatarProfile.Bones); avatarProfile.Reach = Math.Max(2.5f, num3 * 1.25f); StringBuilder stringBuilder = new StringBuilder(); for (int k = 0; k < avatarProfile.Skinned.Count; k++) { stringBuilder.Append((k == 0) ? "" : ", ").Append(avatarProfile.Skinned[k].Name); } Main.Log.Msg("Afterimage profile built: " + avatarProfile.Skinned.Count + " skinned mesh(es) [" + stringBuilder?.ToString() + "], " + avatarProfile.Statics.Count + " static mesh(es), " + avatarProfile.Bones.Length + " bones, size " + num3.ToString("0.0") + " m" + (((Object)(object)val != (Object)null) ? ", head found" : ", no head bone found")); return avatarProfile; int IndexOf(Transform t) { if ((Object)(object)t == (Object)null) { return -1; } if (unionIndex.TryGetValue(((Il2CppObjectBase)t).Pointer, out var value)) { return value; } int count = union.Count; union.Add(t); unionIndex[((Il2CppObjectBase)t).Pointer] = count; return count; } } private static float SkeletonSize(Transform[] bones) { //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) bool flag = true; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(0f, 0f, 0f); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(0f, 0f, 0f); for (int i = 0; i < bones.Length; i++) { if ((Object)(object)bones[i] == (Object)null) { continue; } Vector3 position = bones[i].position; if (flag) { val = position; val2 = position; flag = false; continue; } if (position.x < val.x) { val.x = position.x; } if (position.y < val.y) { val.y = position.y; } if (position.z < val.z) { val.z = position.z; } if (position.x > val2.x) { val2.x = position.x; } if (position.y > val2.y) { val2.y = position.y; } if (position.z > val2.z) { val2.z = position.z; } } return (float)Math.Sqrt(FxMath.DistanceSq(val, val2)); } private static Transform FindHead(GameObject avatar) { try { Transform val = FindHumanoidHead(avatar); if ((Object)(object)val != (Object)null) { return val; } } catch (Exception ex) { Main.Log.Warning("Humanoid head lookup failed, searching by name instead: " + ex.Message); } foreach (Transform componentsInChild in avatar.GetComponentsInChildren<Transform>(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { string name = ((Object)componentsInChild).name; if (name.IndexOf("head", StringComparison.OrdinalIgnoreCase) >= 0 && name.IndexOf("end", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("top", StringComparison.OrdinalIgnoreCase) < 0) { return componentsInChild; } } } return null; } [MethodImpl(MethodImplOptions.NoInlining)] private static Transform FindHumanoidHead(GameObject avatar) { Animator componentInChildren = avatar.GetComponentInChildren<Animator>(true); if ((Object)(object)componentInChildren == (Object)null) { return null; } return componentInChildren.GetBoneTransform((HumanBodyBones)10); } private static int SubMeshCount(Mesh mesh) { if (_meshInfoBroken || (Object)(object)mesh == (Object)null) { return 1; } try { return Math.Max(1, ReadSubMeshCount(mesh)); } catch (Exception ex) { _meshInfoBroken = true; Main.Log.Warning("Couldn't read sub-mesh counts (afterimages use one material slot): " + ex.Message); return 1; } } [MethodImpl(MethodImplOptions.NoInlining)] private static int ReadSubMeshCount(Mesh mesh) { return mesh.subMeshCount; } private static int BlendShapeCount(Mesh mesh) { if (_blendShapesBroken || (Object)(object)mesh == (Object)null) { return 0; } try { return ReadBlendShapeCount(mesh); } catch (Exception ex) { _blendShapesBroken = true; Main.Log.Warning("Couldn't read blend shapes (afterimages keep a neutral face): " + ex.Message); return 0; } } [MethodImpl(MethodImplOptions.NoInlining)] private static int ReadBlendShapeCount(Mesh mesh) { return mesh.blendShapeCount; } private static void CopyBlendShapes(SkinnedMeshRenderer from, SkinnedMeshRenderer to, int count) { if (_blendShapesBroken) { return; } try { CopyBlendShapeWeights(from, to, count); } catch (Exception ex) { _blendShapesBroken = true; Main.Log.Warning("Couldn't copy blend shapes (afterimages keep a neutral face): " + ex.Message); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void CopyBlendShapeWeights(SkinnedMeshRenderer from, SkinnedMeshRenderer to, int count) { for (int i = 0; i < count; i++) { to.SetBlendShapeWeight(i, from.GetBlendShapeWeight(i)); } } } internal static class CameraLocator { private const float MaxDistanceFromHead = 1f; private static Camera _camera; private static float _nextLookup; public static void Reset() { _camera = null; _nextLookup = 0f; } public static Camera Get() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0084: 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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) float now = FxClock.Now; if (now < _nextLookup) { if (!FxMath.Alive((Object)(object)_camera)) { return null; } if (((Behaviour)_camera).isActiveAndEnabled) { return _camera; } } _nextLookup = now + 1f; Transform head = Player.Head; bool flag = FxMath.Alive((Object)(object)head); Vector3 b = (Vector3)(flag ? head.position : new Vector3(0f, 0f, 0f)); Camera val = null; Camera main = Camera.main; if (Usable(main) && (!flag || FxMath.DistanceSq(((Component)main).transform.position, b) <= 1f)) { val = main; } else { float num = float.MaxValue; RigManager rigManager = Player.RigManager; if (FxMath.Alive((Object)(object)rigManager)) { foreach (Camera componentsInChild in ((Component)rigManager).GetComponentsInChildren<Camera>(false)) { if (Usable(componentsInChild)) { float num2 = (flag ? FxMath.DistanceSq(((Component)componentsInChild).transform.position, b) : 0f); if (num2 < num) { num = num2; val = componentsInChild; } } } } if ((Object)(object)val == (Object)null && Usable(main)) { val = main; } } _camera = val; return val; } private static bool Usable(Camera cam) { if (FxMath.Alive((Object)(object)cam) && ((Behaviour)cam).isActiveAndEnabled) { return (Object)(object)cam.targetTexture == (Object)null; } return false; } } internal static class FxClock { private static readonly Stopwatch Watch = Stopwatch.StartNew(); public static float Now => (float)Watch.Elapsed.TotalSeconds; } internal static class FxLog { private const int MaxPerSource = 3; private static readonly Dictionary<string, int> Counts = new Dictionary<string, int>(); public static void Error(string where, Exception e) { Counts.TryGetValue(where, out var value); value++; Counts[where] = value; if (Main.Log != null) { if (value <= 3) { Main.Log.Error(where + " failed: " + e); } else if (value == 4) { Main.Log.Error(where + " keeps failing; further errors from it are hidden."); } } } } internal static class FxMath { public static bool Alive(Object obj) { if (obj != (Object)null) { return !((Il2CppObjectBase)obj).WasCollected; } return false; } public static bool IsDescendantOf(Transform child, Transform ancestor) { if ((Object)(object)child == (Object)null || (Object)(object)ancestor == (Object)null) { return false; } IntPtr pointer = ((Il2CppObjectBase)ancestor).Pointer; Transform val = child; for (int i = 0; i < 256; i++) { if (!((Object)(object)val != (Object)null)) { break; } if (((Il2CppObjectBase)val).Pointer == pointer) { return true; } val = val.parent; } return false; } public static void AttachAtOrigin(Transform child, Transform parent) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) child.SetParent(parent); child.localPosition = new Vector3(0f, 0f, 0f); child.localRotation = Quaternion.identity; child.localScale = new Vector3(1f, 1f, 1f); } public static float Clamp01(float v) { if (v < 0f) { return 0f; } if (!(v > 1f)) { return v; } return 1f; } public static float MoveTowards(float current, float target, float maxDelta) { float value = target - current; if (Math.Abs(value) <= maxDelta) { return target; } return current + (float)Math.Sign(value) * maxDelta; } public static float SmoothStep01(float t) { t = Clamp01(t); return t * t * (3f - 2f * t); } public static float DistanceSq(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.y - b.y; float num3 = a.z - b.z; return num * num + num2 * num2 + num3 * num3; } public static Color Lerp(Color a, Color b, float t) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_0060: Unknown result type (might be due to invalid IL or missing references) t = Clamp01(t); return new Color(a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t, a.b + (b.b - a.b) * t, a.a + (b.a - a.a) * t); } public static Color HsvToRgb(float h, float s, float v) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) h -= (float)Math.Floor(h); s = Clamp01(s); v = Clamp01(v); float num = h * 6f; int num2 = (int)Math.Floor(num) % 6; float num3 = num - (float)Math.Floor(num); float num4 = v * (1f - s); float num5 = v * (1f - num3 * s); float num6 = v * (1f - (1f - num3) * s); return (Color)(num2 switch { 0 => new Color(v, num6, num4, 1f), 1 => new Color(num5, v, num4, 1f), 2 => new Color(num4, v, num6, 1f), 3 => new Color(num4, num5, v, 1f), 4 => new Color(num6, num4, v, 1f), _ => new Color(v, num4, num5, 1f), }); } public static bool TryParseHexColor(string text, out Color color) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) color = new Color(1f, 1f, 1f, 1f); if (string.IsNullOrEmpty(text)) { return false; } string text2 = text.Trim(); if (text2.StartsWith("#", StringComparison.Ordinal)) { text2 = text2.Substring(1); } if (text2.Length == 3) { text2 = new string(new char[6] { text2[0], text2[0], text2[1], text2[1], text2[2], text2[2] }); } if (text2.Length != 6 && text2.Length != 8) { return false; } int[] array = new int[4] { 0, 0, 0, 255 }; for (int i = 0; i < text2.Length / 2; i++) { int num = HexDigit(text2[i * 2]); int num2 = HexDigit(text2[i * 2 + 1]); if (num < 0 || num2 < 0) { return false; } array[i] = num * 16 + num2; } color = new Color((float)array[0] / 255f, (float)array[1] / 255f, (float)array[2] / 255f, (float)array[3] / 255f); return true; } private static int HexDigit(char c) { if (c >= '0' && c <= '9') { return c - 48; } if (c >= 'a' && c <= 'f') { return c - 97 + 10; } if (c >= 'A' && c <= 'F') { return c - 65 + 10; } return -1; } } public static class BuildInfo { public const string Name = "SandevistanFX"; public const string Version = "1.5.0"; public const string Author = "YourName"; } public sealed class Main : MelonMod { private SandevistanController _controller; internal static Instance Log { get; private set; } public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; Settings.Init(); _controller = new SandevistanController(); Hooking.OnLevelUnloaded += delegate { _controller.OnLevelUnloaded(); }; Hooking.OnLevelLoaded += delegate { _controller.OnLevelLoaded(); }; Hooking.OnSwitchAvatarPostfix += delegate { _controller.OnAvatarChanged(); }; Hooking.OnGrabObject += delegate { _controller.RequestRescan(); }; Hooking.OnReleaseObject += delegate { _controller.RequestRescan(); }; Log.Msg("Loaded. Hit slow-mo to go Sandevistan."); } public override void OnLateInitializeMelon() { if (!IsAssemblyLoaded("Quicksilver")) { Log.Warning("Quicksilver isn't installed. SandevistanFX requires it so you can move at full speed during slow-mo: https://thunderstore.io/c/bonelab/p/Lakatrazz/Quicksilver/"); } try { MenuSetup.Create(_controller); } catch (Exception ex) { Log.Warning("BoneMenu page could not be created (settings still work through MelonPreferences.cfg): " + ex.Message); } } private static bool IsAssemblyLoaded(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { if (string.Equals(assemblies[i].GetName().Name, name, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public override void OnUpdate() { if (_controller == null) { return; } try { _controller.Update(); } catch (Exception e) { FxLog.Error("Update", e); } } public override void OnLateUpdate() { if (_controller == null) { return; } try { _controller.LateUpdate(); } catch (Exception e) { FxLog.Error("LateUpdate", e); } } } internal enum BlendKind { Alpha, Additive, Multiply } internal static class MaterialFactory { private const string UrpUnlitName = "Universal Render Pipeline/Unlit"; private static readonly string[] FallbackNames = new string[2] { "UI/Default", "Sprites/Default" }; private const float BlendZero = 0f; private const float BlendOne = 1f; private const float BlendDstColor = 2f; private const float BlendSrcAlpha = 5f; private const float BlendOneMinusSrcAlpha = 10f; private const float CullOff = 0f; private const float CullBack = 2f; private const float ZTestLessEqual = 4f; private const float ZTestAlways = 8f; private static Shader _urpUnlit; private static Shader _fallback; private static float _nextSearch; private static bool _reported; private static bool _keywordsBroken; public static int Generation { get; private set; } = 1; public static bool UsesFreeBlend { get { bool result = FxMath.Alive((Object)(object)_urpUnlit); bool flag = FxMath.Alive((Object)(object)_fallback); if (Settings.UseFallbackShader.Value && flag) { return false; } return result; } } public static void BumpGeneration() { Generation++; } public static bool Ready() { if (FxMath.Alive((Object)(object)_urpUnlit) || FxMath.Alive((Object)(object)_fallback)) { return true; } float now = FxClock.Now; if (now < _nextSearch) { return false; } _nextSearch = now + 3f; _urpUnlit = FindShader("Universal Render Pipeline/Unlit"); _fallback = null; string[] fallbackNames = FallbackNames; for (int i = 0; i < fallbackNames.Length; i++) { _fallback = FindShader(fallbackNames[i]); if ((Object)(object)_fallback != (Object)null) { break; } } bool flag = (Object)(object)_urpUnlit != (Object)null || (Object)(object)_fallback != (Object)null; if (!_reported) { _reported = true; if (flag) { Main.Log.Msg("Using shader: " + (((Object)(object)_urpUnlit != (Object)null) ? "Universal Render Pipeline/Unlit" : ((Object)_fallback).name) + (((Object)(object)_fallback != (Object)null) ? (" (fallback available: " + ((Object)_fallback).name + ")") : "")); } else { Main.Log.Warning("No usable unlit shader found yet; will keep looking."); } } return flag; } public static Material Create(string name, BlendKind kind, Color color, int queue, bool cullOff, bool alwaysOnTop) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) if (!Ready()) { return null; } bool usesFreeBlend = UsesFreeBlend; Shader val = (usesFreeBlend ? _urpUnlit : (FxMath.Alive((Object)(object)_fallback) ? _fallback : _urpUnlit)); if (!FxMath.Alive((Object)(object)val)) { return null; } Material val2 = new Material(val); ((Object)val2).name = "[SandevistanFX] " + name; ((Object)val2).hideFlags = (HideFlags)61; if (usesFreeBlend) { float value; float value2; switch (kind) { case BlendKind.Multiply: value = 2f; value2 = 0f; break; case BlendKind.Additive: value = 5f; value2 = 1f; break; default: value = 5f; value2 = 10f; break; } SetFloat(val2, "_Surface", 1f); SetFloat(val2, "_AlphaClip", 0f); SetFloat(val2, "_ZWrite", 0f); SetFloat(val2, "_Cull", cullOff ? 0f : 2f); SetFloat(val2, "_SrcBlend", value); SetFloat(val2, "_DstBlend", value2); SetFloat(val2, "_SrcBlendAlpha", 1f); SetFloat(val2, "_DstBlendAlpha", 10f); if (alwaysOnTop) { SetFloat(val2, "_ZTest", 8f); } ApplyTransparentKeywords(val2); } else { SetFloat(val2, "unity_GUIZTestMode", alwaysOnTop ? 8f : 4f); } val2.renderQueue = queue; SetColor(val2, color); return val2; } public static void SetColor(Material material, Color color) { //IL_0010: 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) if (!((Object)(object)material == (Object)null)) { material.SetColor("_BaseColor", color); material.SetColor("_Color", color); } } public static void Destroy(Material material) { if (FxMath.Alive((Object)(object)material)) { Object.Destroy((Object)(object)material); } } private static void SetFloat(Material material, string property, float value) { material.SetFloat(Shader.PropertyToID(property), value); } private static void ApplyTransparentKeywords(Material material) { if (_keywordsBroken) { return; } try { SetKeywords(material); } catch (Exception ex) { _keywordsBroken = true; Main.Log.Warning("Couldn't set shader keywords (continuing without them): " + ex.Message); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void SetKeywords(Material material) { material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); material.DisableKeyword("_ALPHATEST_ON"); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.DisableKeyword("_ALPHAMODULATE_ON"); } private static Shader FindShader(string name) { Shader val = Shader.Find(name); if (FxMath.Alive((Object)(object)val)) { return val; } foreach (Shader item in Resources.FindObjectsOfTypeAll<Shader>()) { if ((Object)(object)item != (Object)null && ((Object)item).name == name) { return item; } } return null; } } internal static class MenuSetup { public static void Create(SandevistanController controller) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_0463: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Unknown result type (might be due to invalid IL or missing references) if (Page.Root != null) { Color val = default(Color); ((Color)(ref val))..ctor(0.24f, 1f, 0.45f, 1f); Color val2 = default(Color); ((Color)(ref val2))..ctor(1f, 1f, 1f, 1f); Color val3 = default(Color); ((Color)(ref val3))..ctor(1f, 0.92f, 0.2f, 1f); Page obj = Page.Root.CreatePage("Sandevistan FX", val, 0, true); obj.CreateBool("Enabled", val2, Settings.Enabled.Value, (Action<bool>)delegate(bool v) { Settings.Set(Settings.Enabled, v); }); obj.CreateBool("Preview (force on)", val3, false, (Action<bool>)delegate(bool v) { controller.Preview = v; }); Page obj2 = obj.CreatePage("Color Grade", val, 0, true); obj2.CreateFloat("Strength", val2, Settings.GradeStrength.Value, 0.05f, 0f, 1f, (Action<float>)delegate(float v) { Settings.Set(Settings.GradeStrength, v); }); obj2.CreateFloat("Glow", val2, Settings.GlowStrength.Value, 0.02f, 0f, 1f, (Action<float>)delegate(float v) { Settings.Set(Settings.GlowStrength, v); }); obj2.CreateFloat("Activation Flash", val2, Settings.ActivationFlash.Value, 0.1f, 0f, 1f, (Action<float>)delegate(float v) { Settings.Set(Settings.ActivationFlash, v); }); obj2.CreateFloat("Fade In (s)", val2, Settings.FadeInTime.Value, 0.05f, 0f, 2f, (Action<float>)delegate(float v) { Settings.Set(Settings.FadeInTime, v); }); obj2.CreateFloat("Fade Out (s)", val2, Settings.FadeOutTime.Value, 0.05f, 0f, 2f, (Action<float>)delegate(float v) { Settings.Set(Settings.FadeOutTime, v); }); obj2.CreateBool("Keep Player Colors", val2, Settings.KeepPlayerColors.Value, (Action<bool>)delegate(bool v) { Settings.Set(Settings.KeepPlayerColors, v); }); obj2.CreateBool("Use Fallback Shader", val2, Settings.UseFallbackShader.Value, (Action<bool>)delegate(bool v) { Settings.Set(Settings.UseFallbackShader, v); controller.OnMaterialSettingsChanged(); }); Page obj3 = obj.CreatePage("Afterimages", val, 0, true); obj3.CreateBool("Enabled", val2, Settings.AfterimagesEnabled.Value, (Action<bool>)delegate(bool v) { Settings.Set(Settings.AfterimagesEnabled, v); }); obj3.CreateFloat("Spawn Interval (s)", val2, Settings.AfterimageInterval.Value, 0.01f, 0.02f, 0.5f, (Action<float>)delegate(float v) { Settings.Set(Settings.AfterimageInterval, v); }); obj3.CreateFloat("Lifetime (s)", val2, Settings.AfterimageLifetime.Value, 0.25f, 0.25f, 10f, (Action<float>)delegate(float v) { Settings.Set(Settings.AfterimageLifetime, v); }); obj3.CreateFloat("Opacity", val2, Settings.AfterimageOpacity.Value, 0.05f, 0.05f, 1f, (Action<float>)delegate(float v) { Settings.Set(Settings.AfterimageOpacity, v); }); obj3.CreateFloat("Rainbow Speed", val2, Settings.AfterimageHueSpeed.Value, 0.05f, 0f, 2f, (Action<float>)delegate(float v) { Settings.Set(Settings.AfterimageHueSpeed, v); }); obj3.CreateFloat("Saturation", val2, Settings.AfterimageSaturation.Value, 0.05f, 0f, 1f, (Action<float>)delegate(float v) { Settings.Set(Settings.AfterimageSaturation, v); }); obj3.CreateInt("Max Count", val2, Settings.AfterimageMaxCount.Value, 5, 5, 100, (Action<int>)delegate(int v) { Settings.Set(Settings.AfterimageMaxCount, v); }); obj3.CreateBool("Include Items", val2, Settings.AfterimageIncludeItems.Value, (Action<bool>)delegate(bool v) { Settings.Set(Settings.AfterimageIncludeItems, v); }); obj3.CreateBool("Glowing (Additive)", val2, Settings.AfterimageAdditive.Value, (Action<bool>)delegate(bool v) { Settings.Set(Settings.AfterimageAdditive, v); controller.OnMaterialSettingsChanged(); }); } } } internal sealed class PlayerColorShield { private readonly Dictionary<IntPtr, Renderer> _protected = new Dictionary<IntPtr, Renderer>(); private readonly Dictionary<IntPtr, Material> _cloneByOriginal = new Dictionary<IntPtr, Material>(); private readonly Dictionary<IntPtr, Material> _originalByClone = new Dictionary<IntPtr, Material>(); private readonly HashSet<IntPtr> _seen = new HashSet<IntPtr>(); private readonly List<IntPtr> _stale = new List<IntPtr>(); public bool IsActive { get { if (_protected.Count <= 0) { return _cloneByOriginal.Count > 0; } return true; } } public void Apply(List<Renderer> renderers) { _seen.Clear(); for (int i = 0; i < renderers.Count; i++) { Renderer val = renderers[i]; if (FxMath.Alive((Object)(object)val)) { IntPtr pointer = ((Il2CppObjectBase)val).Pointer; if (_seen.Add(pointer)) { EnsureProtected(val); _protected[pointer] = val; } } } _stale.Clear(); foreach (KeyValuePair<IntPtr, Renderer> item in _protected) { if (!_seen.Contains(item.Key)) { _stale.Add(item.Key); } } for (int j = 0; j < _stale.Count; j++) { Restore(_protected[_stale[j]]); _protected.Remove(_stale[j]); } } public void RestoreAll(bool destroyClones) { foreach (KeyValuePair<IntPtr, Renderer> item in _protected) { Restore(item.Value); } _protected.Clear(); if (!destroyClones) { return; } foreach (KeyValuePair<IntPtr, Material> item2 in _cloneByOriginal) { if (FxMath.Alive((Object)(object)item2.Value)) { Object.Destroy((Object)(object)item2.Value); } } _cloneByOriginal.Clear(); _originalByClone.Clear(); } private void EnsureProtected(Renderer renderer) { Il2CppReferenceArray<Material> sharedMaterials = renderer.sharedMaterials; int length = ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; if (length == 0) { return; } Material[] array = null; for (int i = 0; i < length; i++) { Material val = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[i]; if ((Object)(object)val == (Object)null || _originalByClone.ContainsKey(((Il2CppObjectBase)val).Pointer)) { continue; } if (array == null) { array = (Material[])(object)new Material[length]; for (int j = 0; j < length; j++) { array[j] = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[j]; } } array[i] = CloneFor(val); } if (array != null) { renderer.sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit(array); } } private void Restore(Renderer renderer) { if (!FxMath.Alive((Object)(object)renderer)) { return; } Il2CppReferenceArray<Material> sharedMaterials = renderer.sharedMaterials; int length = ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; Material[] array = null; for (int i = 0; i < length; i++) { Material val = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[i]; if ((Object)(object)val == (Object)null || !_originalByClone.TryGetValue(((Il2CppObjectBase)val).Pointer, out var value)) { continue; } if (array == null) { array = (Material[])(object)new Material[length]; for (int j = 0; j < length; j++) { array[j] = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[j]; } } array[i] = value; } if (array != null) { renderer.sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit(array); } } private Material CloneFor(Material original) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown IntPtr pointer = ((Il2CppObjectBase)original).Pointer; if (_cloneByOriginal.TryGetValue(pointer, out var value) && FxMath.Alive((Object)(object)value)) { return value; } Material val = new Material(original); ((Object)val).name = ((Object)original).name + " [SandevistanFX]"; ((Object)val).hideFlags = (HideFlags)61; val.renderQueue = RenderOrder.PlayerQueue(original.renderQueue); _cloneByOriginal[pointer] = val; _originalByClone[((Il2CppObjectBase)val).Pointer] = original; return val; } } internal sealed class PlayerScanner { public readonly List<Renderer> Renderers = new List<Renderer>(); public readonly List<GameObject> ItemRoots = new List<GameObject>(); private readonly HashSet<IntPtr> _itemKeys = new HashSet<IntPtr>(); private readonly Dictionary<IntPtr, bool> _npcCache = new Dictionary<IntPtr, bool>(); private readonly List<InventorySlotReceiver> _slots = new List<InventorySlotReceiver>(); private IntPtr _slotsRig = IntPtr.Zero; private float _slotsRefreshAt; private Vector3 _eye; private bool _hasEye; private const float MaxItemDistance = 4f; public void Reset() { Renderers.Clear(); ItemRoots.Clear(); _itemKeys.Clear(); _npcCache.Clear(); _slots.Clear(); _slotsRig = IntPtr.Zero; _slotsRefreshAt = 0f; } public void Scan(Func<Renderer, bool> exclude, Camera camera) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) Renderers.Clear(); ItemRoots.Clear(); _itemKeys.Clear(); Transform head = Player.Head; _hasEye = FxMath.Alive((Object)(object)head) || FxMath.Alive((Object)(object)camera); if (FxMath.Alive((Object)(object)head)) { _eye = head.position; } else if (_hasEye) { _eye = ((Component)camera).transform.position; } RigManager rigManager = Player.RigManager; if (FxMath.Alive((Object)(object)rigManager)) { CollectHeld(Player.LeftHand, rigManager); CollectHeld(Player.RightHand, rigManager); CollectHolstered(rigManager); AddRenderers(((Component)rigManager).gameObject, exclude); Avatar avatar = rigManager.avatar; if (FxMath.Alive((Object)(object)avatar)) { AddRenderers(((Component)avatar).gameObject, exclude); } for (int i = 0; i < ItemRoots.Count; i++) { AddRenderers(ItemRoots[i], exclude); } } } private void CollectHeld(Hand hand, RigManager rig) { if (FxMath.Alive((Object)(object)hand)) { GameObject objectInHand = Player.GetObjectInHand(hand); if (FxMath.Alive((Object)(object)objectInHand)) { AddEntityOf(objectInHand.transform, rig); } } } private void CollectHolstered(RigManager rig) { float now = FxClock.Now; if (_slotsRig != ((Il2CppObjectBase)rig).Pointer || now >= _slotsRefreshAt) { _slots.Clear(); foreach (InventorySlotReceiver componentsInChild in ((Component)rig).GetComponentsInChildren<InventorySlotReceiver>(true)) { if ((Object)(object)componentsInChild != (Object)null) { _slots.Add(componentsInChild); } } _slotsRig = ((Il2CppObjectBase)rig).Pointer; _slotsRefreshAt = now + 5f; } for (int i = 0; i < _slots.Count; i++) { InventorySlotReceiver val = _slots[i]; if (FxMath.Alive((Object)(object)val)) { WeaponSlot slottedWeapon = val._slottedWeapon; if (FxMath.Alive((Object)(object)slottedWeapon)) { AddEntityOf(((Component)slottedWeapon).transform, rig); } } } } private void AddEntityOf(Transform part, RigManager rig) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (_hasEye && FxMath.DistanceSq(part.position, _eye) > 16f) { return; } MarrowEntity componentInParent = ((Component)part).GetComponentInParent<MarrowEntity>(); if (!FxMath.Alive((Object)(object)componentInParent)) { return; } Hand val = Player.LeftHand; if (!FxMath.Alive((Object)(object)val)) { val = Player.RightHand; } if ((!FxMath.Alive((Object)(object)val) || !FxMath.IsDescendantOf(((Component)val).transform, ((Component)componentInParent).transform)) && !IsNpc(componentInParent)) { GameObject gameObject = ((Component)componentInParent).gameObject; if (_itemKeys.Add(((Il2CppObjectBase)gameObject).Pointer)) { ItemRoots.Add(gameObject); } } } private bool IsNpc(MarrowEntity entity) { IntPtr pointer = ((Il2CppObjectBase)entity).Pointer; if (_npcCache.TryGetValue(pointer, out var value)) { return value; } bool flag = (Object)(object)((Component)entity).GetComponentInChildren<AIBrain>(true) != (Object)null; if (_npcCache.Count > 512) { _npcCache.Clear(); } _npcCache[pointer] = flag; return flag; } private void AddRenderers(GameObject root, Func<Renderer, bool> exclude) { if (!FxMath.Alive((Object)(object)root)) { return; } foreach (Renderer componentsInChild in root.GetComponentsInChildren<Renderer>(true)) { if (!((Object)(object)componentsInChild == (Object)null) && !exclude(componentsInChild)) { Renderers.Add(componentsInChild); } } } } internal static class RenderOrder { public const int GradeTint = 3990; public const int GradeGlow = 3991; public const int Afterimage = 4550; public static int PlayerQueue(int originalQueue) { if (originalQueue < 0) { originalQueue = 2000; } if (originalQueue > 5000) { originalQueue = 5000; } return 4000 + originalQueue / 5; } } internal static class RendererUtil { private static bool _shadowsBroken; public static void DisableShadows(Renderer renderer) { if (_shadowsBroken || (Object)(object)renderer == (Object)null) { return; } try { SetShadowsOff(renderer); } catch (Exception ex) { _shadowsBroken = true; Main.Log.Warning("Couldn't turn off effect shadows (harmless): " + ex.Message); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void SetShadowsOff(Renderer renderer) { renderer.shadowCastingMode = (ShadowCastingMode)0; } public static Material[] Repeat(Material material, int count) { Material[] array = (Material[])(object)new Material[Math.Max(1, count)]; for (int i = 0; i < array.Length; i++) { array[i] = material; } return array; } } internal sealed class SandevistanController { private const float ScanInterval = 0.2f; private const float FlashDuration = 0.3f; private readonly PlayerScanner _scanner = new PlayerScanner(); private readonly PlayerColorShield _shield = new PlayerColorShield(); private readonly ScreenGrade _grade = new ScreenGrade(); private readonly AfterimageSystem _afterimages = new AfterimageSystem(); private readonly Func<Renderer, bool> _isOwnRenderer; private bool _active; private float _weight; private float _flash; private float _nextScan; private bool _rescanRequested; private int _frame; private float _lastUpdate = -1f; public bool Preview { get; set; } public SandevistanController() { _isOwnRenderer = _grade.Owns; } public void Update() { float now = FxClock.Now; float num = ((_lastUpdate < 0f) ? 0f : (now - _lastUpdate)); _lastUpdate = now; if (num > 0.1f) { num = 0.1f; } bool value = Settings.Enabled.Value; bool flag = FxMath.Alive((Object)(object)Player.RigManager); bool flag2 = value && flag && (Preview || SlowMoDetector.IsSlowMoActive()); if (flag2 && !_active) { Activate(); } else if (!flag2 && _active) { Deactivate(); } float num2 = (_active ? 1f : 0f); float num3 = (_active ? Settings.FadeInTime.Value : Settings.FadeOutTime.Value); _weight = ((num3 <= 0.0001f) ? num2 : FxMath.MoveTowards(_weight, num2, num / num3)); _flash = FxMath.MoveTowards(_flash, 0f, num / 0.3f); if (!value || !flag) { _weight = 0f; _flash = 0f; } bool flag3 = _weight > 0.0001f || _flash > 0.0001f; try { if (flag3) { if (_rescanRequested || now >= _nextScan) { _rescanRequested = false; _nextScan = now + 0.2f; _scanner.Scan(_isOwnRenderer, CameraLocator.Get()); if (Settings.KeepPlayerColors.Value) { _shield.Apply(_scanner.Renderers); } else if (_shield.IsActive) { _shield.RestoreAll(destroyClones: true); } } } else if (_shield.IsActive) { _shield.RestoreAll(destroyClones: true); } } catch (Exception e) { FxLog.Error("Keeping player colors", e); } try { _grade.Update(CameraLocator.Get(), _weight, _flash); } catch (Exception e2) { FxLog.Error("Color grade", e2); } _frame++; if (!_active && value && flag && _frame % 8 == 0) { try { _afterimages.Prewarm(); } catch (Exception e3) { FxLog.Error("Preparing afterimages", e3); } } } public void LateUpdate() { if (!_active) { return; } try { if (!Settings.AfterimagesEnabled.Value) { _afterimages.HideAll(); } else { _afterimages.Tick(_scanner, CameraLocator.Get()); } } catch (Exception e) { FxLog.Error("Afterimages", e); SafeHideAfterimages(); } } private void SafeHideAfterimages() { try { _afterimages.HideAll(); } catch (Exception e) { FxLog.Error("Hiding afterimages", e); } } public void RequestRescan() { _rescanRequested = true; } public void OnAvatarChanged() { try { _afterimages.OnAvatarChanged(); } catch (Exception e) { FxLog.Error("Avatar change", e); } _rescanRequested = true; } public void OnMaterialSettingsChanged() { MaterialFactory.BumpGeneration(); try { _afterimages.OnMaterialSettingsChanged(); } catch (Exception e) { FxLog.Error("Applying settings", e); } } public void OnLevelUnloaded() { _active = false; _weight = 0f; _flash = 0f; try { _shield.RestoreAll(destroyClones: true); } catch (Exception e) { FxLog.Error("Level unload (colors)", e); } try { _afterimages.DestroyAll(); } catch (Exception e2) { FxLog.Error("Level unload (afterimages)", e2); } try { _grade.Destroy(); } catch (Exception e3) { FxLog.Error("Level unload (grade)", e3); } _scanner.Reset(); CameraLocator.Reset(); } public void OnLevelLoaded() { _scanner.Reset(); CameraLocator.Reset(); _rescanRequested = true; } private void Activate() { _active = true; _rescanRequested = true; _flash = 1f; SafeHideAfterimages(); } private void Deactivate() { _active = false; _flash = 0f; SafeHideAfterimages(); } } internal sealed class ScreenGrade { private GameObject _root; private Camera _camera; private MeshRenderer _tint; private MeshRenderer _glow; private Material _tintMaterial; private Material _glowMaterial; private bool _freeBlend; private int _generation; private readonly HashSet<IntPtr> _ownRenderers = new HashSet<IntPtr>(); public bool Owns(Renderer renderer) { if ((Object)(object)renderer != (Object)null) { return _ownRenderers.Contains(((Il2CppObjectBase)renderer).Pointer); } return false; } public void Update(Camera camera, float weight, float flash) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) if (!(weight > 0.0001f) && !(flash > 0.0001f)) { if (FxMath.Alive((Object)(object)_root) && _root.activeSelf) { _root.SetActive(false); } return; } if (!FxMath.Alive((Object)(object)camera) || !EnsureBuilt(camera)) { if (FxMath.Alive((Object)(object)_root) && _root.activeSelf) { _root.SetActive(false); } return; } if (!_root.activeSelf) { _root.SetActive(true); } float num = Math.Max(_camera.nearClipPlane * 2f, 0.025f); _root.transform.localPosition = new Vector3(0f, 0f, num); _root.transform.localRotation = Quaternion.identity; Color gradeColorValue = Settings.GradeColorValue; float num2 = FxMath.Clamp01(Settings.GradeStrength.Value) * weight; float num3 = flash * FxMath.Clamp01(Settings.ActivationFlash.Value); if (_freeBlend) { Color color = FxMath.Lerp(new Color(1f, 1f, 1f, 1f), gradeColorValue, num2); color.a = 1f; MaterialFactory.SetColor(_tintMaterial, color); float num4 = FxMath.Clamp01(Settings.GlowStrength.Value) * weight + num3 * 0.6f; bool flag = num4 > 0.001f; ((Renderer)_glow).enabled = flag; if (flag) { MaterialFactory.SetColor(_glowMaterial, new Color(gradeColorValue.r, gradeColorValue.g, gradeColorValue.b, FxMath.Clamp01(num4))); } } else { float num5 = FxMath.Clamp01(num2 * 0.45f + num3 * 0.35f); MaterialFactory.SetColor(_tintMaterial, new Color(gradeColorValue.r, gradeColorValue.g, gradeColorValue.b, num5)); ((Renderer)_glow).enabled = false; } } public void Destroy() { if (FxMath.Alive((Object)(object)_root)) { Object.Destroy((Object)(object)_root); } MaterialFactory.Destroy(_tintMaterial); MaterialFactory.Destroy(_glowMaterial); _root = null; _camera = null; _tint = null; _glow = null; _tintMaterial = null; _glowMaterial = null; _ownRenderers.Clear(); } private bool EnsureBuilt(Camera camera) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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_0128: Expected O, but got Unknown if (FxMath.Alive((Object)(object)_root) && FxMath.Alive((Object)(object)_camera) && ((Il2CppObjectBase)_camera).Pointer == ((Il2CppObjectBase)camera).Pointer && FxMath.Alive((Object)(object)_tintMaterial) && FxMath.Alive((Object)(object)_glowMaterial) && _generation == MaterialFactory.Generation) { return true; } Destroy(); if (!MaterialFactory.Ready()) { return false; } _freeBlend = MaterialFactory.UsesFreeBlend; _tintMaterial = MaterialFactory.Create("Grade", _freeBlend ? BlendKind.Multiply : BlendKind.Alpha, new Color(1f, 1f, 1f, 0f), 3990, cullOff: true, alwaysOnTop: true); _glowMaterial = MaterialFactory.Create("Glow", BlendKind.Additive, new Color(0f, 0f, 0f, 0f), 3991, cullOff: true, alwaysOnTop: true); if ((Object)(object)_tintMaterial == (Object)null || (Object)(object)_glowMaterial == (Object)null) { Destroy(); return false; } _camera = camera; _generation = MaterialFactory.Generation; _root = new GameObject("[SandevistanFX] Screen Grade"); _root.layer = PickLayer(camera); FxMath.AttachAtOrigin(_root.transform, ((Component)camera).transform); _tint = CreateQuad("Tint", _tintMaterial); _glow = CreateQuad("Glow", _glowMaterial); return true; } private MeshRenderer CreateQuad(string name, Material material) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GameObject.CreatePrimitive((PrimitiveType)5); Collider component = obj.GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } ((Object)obj).name = "[SandevistanFX] " + name; obj.layer = _root.layer; FxMath.AttachAtOrigin(obj.transform, _root.transform); obj.transform.localScale = new Vector3(6f, 6f, 1f); MeshRenderer component2 = obj.GetComponent<MeshRenderer>(); ((Renderer)component2).sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit(RendererUtil.Repeat(material, 1)); RendererUtil.DisableShadows((Renderer)(object)component2); _ownRenderers.Add(((Il2CppObjectBase)component2).Pointer); return component2; } private static int PickLayer(Camera camera) { int cullingMask = camera.cullingMask; if ((cullingMask & 1) != 0) { return 0; } for (int i = 1; i < 32; i++) { if ((cullingMask & (1 << i)) != 0) { return i; } } return 0; } } internal static class Settings { private static MelonPreferences_Category _category; private const int CurrentConfigVersion = 3; public const float DefaultHueSpeed = 0.2f; public const int DefaultMaxCount = 50; public const float DefaultLifetime = 3f; public const int MaxCountLimit = 100; public static MelonPreferences_Entry<int> ConfigVersion; public static MelonPreferences_Entry<bool> Enabled; public static MelonPreferences_Entry<bool> TriggerOnAnyTimeSlowdown; public static MelonPreferences_Entry<bool> UseFallbackShader; public static MelonPreferences_Entry<string> GradeColor; public static MelonPreferences_Entry<float> GradeStrength; public static MelonPreferences_Entry<float> GlowStrength; public static MelonPreferences_Entry<float> ActivationFlash; public static MelonPreferences_Entry<float> FadeInTime; public static MelonPreferences_Entry<float> FadeOutTime; public static MelonPreferences_Entry<bool> KeepPlayerColors; public static MelonPreferences_Entry<bool> AfterimagesEnabled; public static MelonPreferences_Entry<float> AfterimageInterval; public static MelonPreferences_Entry<float> AfterimageLifetime; public static MelonPreferences_Entry<float> AfterimageOpacity; public static MelonPreferences_Entry<float> AfterimageHueSpeed; public static MelonPreferences_Entry<float> AfterimageSaturation; public static MelonPreferences_Entry<float> AfterimageMinMove; public static MelonPreferences_Entry<float> AfterimageRevealDelay; public static MelonPreferences_Entry<float> AfterimageHeadHideRadius; public static MelonPreferences_Entry<float> AfterimageRevealDistance; public static MelonPreferences_Entry<int> AfterimageMaxCount; public static MelonPreferences_Entry<bool> AfterimageIncludeItems; public static MelonPreferences_Entry<bool> AfterimageAdditive; private static string _parsedColorSource; private static Color _parsedColor = new Color(0.24f, 1f, 0.45f, 1f); public static Color GradeColorValue { get { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) string value = GradeColor.Value; if ((object)value != _parsedColorSource) { _parsedColorSource = value; if (FxMath.TryParseHexColor(value, out var color)) { _parsedColor = color; } else { Main.Log.Warning("GradeColor '" + value + "' isn't a valid hex color, using the default neon green."); _parsedColor = new Color(0.24f, 1f, 0.45f, 1f); } } return _parsedColor; } } public static void Init() { _category = MelonPreferences.CreateCategory("SandevistanFX", "Sandevistan FX"); Enabled = _category.CreateEntry<bool>("Enabled", true, (string)null, "Master switch for the whole effect.", false, false, (ValueValidator)null, (string)null); TriggerOnAnyTimeSlowdown = _category.CreateEntry<bool>("TriggerOnAnyTimeSlowdown", false, (string)null, "Off: only BONELAB's slow-mo ability triggers the effect. On: any slowed time scale does (other slow-mo mods, Fusion, etc.).", false, false, (ValueValidator)null, (string)null); UseFallbackShader = _category.CreateEntry<bool>("UseFallbackShader", false, (string)null, "Troubleshooting: turn on if the green grade or afterimages never show up. Uses a simpler built-in shader (grade becomes a green wash).", false, false, (ValueValidator)null, (string)null); GradeColor = _category.CreateEntry<string>("GradeColor", "#3DFF73", (string)null, "Hex color of the world grade.", false, false, (ValueValidator)null, (string)null); GradeStrength = _category.CreateEntry<float>("GradeStrength", 0.7f, (string)null, "0-1. How hard the world is pushed toward the grade color.", false, false, (ValueValidator)null, (string)null); GlowStrength = _category.CreateEntry<float>("GlowStrength", 0.1f, (string)null, "0-1. Extra neon green light added on top of the grade.", false, false, (ValueValidator)null, (string)null); ActivationFlash = _category.CreateEntry<float>("ActivationFlash", 0.5f, (string)null, "0-1. Brightness of the green flash when slow-mo kicks in.", false, false, (ValueValidator)null, (string)null); FadeInTime = _category.CreateEntry<float>("FadeInTime", 0.12f, (string)null, "Seconds (real time) for the grade to fade in.", false, false, (ValueValidator)null, (string)null); FadeOutTime = _category.CreateEntry<float>("FadeOutTime", 0.15f, (string)null, "Seconds (real time) for the grade to fade out. 0 = instant.", false, false, (ValueValidator)null, (string)null); KeepPlayerColors = _category.CreateEntry<bool>("KeepPlayerColors", true, (string)null, "Keep your body, held weapons/items and holstered gear in their normal colors while the grade is up.", false, false, (ValueValidator)null, (string)null); AfterimagesEnabled = _category.CreateEntry<bool>("AfterimagesEnabled", true, (string)null, "Rainbow afterimages while slow-mo is on.", false, false, (ValueValidator)null, (string)null); AfterimageInterval = _category.CreateEntry<float>("AfterimageInterval", 0.06f, (string)null, "Seconds (real time) between afterimages.", false, false, (ValueValidator)null, (string)null); AfterimageLifetime = _category.CreateEntry<float>("AfterimageLifetime", 3f, (string)null, "Seconds (real time) each afterimage stays before disappearing instantly (no fade).", false, false, (ValueValidator)null, (string)null); AfterimageOpacity = _category.CreateEntry<float>("AfterimageOpacity", 0.5f, (string)null, "0-1. Opacity of each afterimage (constant until it disappears).", false, false, (ValueValidator)null, (string)null); AfterimageHueSpeed = _category.CreateEntry<float>("AfterimageHueSpeed", 0.2f, (string)null, "How fast the afterimage color moves through the rainbow (cycles per second). Lower = slower color change.", false, false, (ValueValidator)null, (string)null); AfterimageSaturation = _category.CreateEntry<float>("AfterimageSaturation", 0.85f, (string)null, "0-1. Color saturation of the afterimages.", false, false, (ValueValidator)null, (string)null); AfterimageMinMove = _category.CreateEntry<float>("AfterimageMinMove", 0.02f, (string)null, "Meters your head, hands or held items must move before a new afterimage spawns.", false, false, (ValueValidator)null, (string)null); AfterimageRevealDelay = _category.CreateEntry<float>("AfterimageRevealDelay", 0f, (string)null, "Seconds an afterimage stays hidden after spawning. 0 = appears instantly.", false, false, (ValueValidator)null, (string)null); AfterimageHeadHideRadius = _category.CreateEntry<float>("AfterimageHeadHideRadius", 0.45f, (string)null, "Afterimage heads are hidden while your eyes are within this many meters of them, so they never block your view.", false,