Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Eclipse AdvancedSkins v0.1.1
BepInEx/plugins/EclipseAdvancedSkins/Eclipse.AdvancedSkins.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading; using EntityStates; using EntityStates.Croco; using On.RoR2; using RoR2; using RoR2.ContentManagement; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Audio; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] namespace Eclipse.AdvancedSkins; public sealed class AdvancedSkinApplicationBridge : IDisposable { private static bool installed; private readonly HashSet<SkinDef> ownedSkins = new HashSet<SkinDef>(); private bool disposed; public AdvancedSkinApplicationBridge() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown if (installed) { throw new InvalidOperationException("Advanced skin bridge already installed"); } SkinDef.ApplyAsync += new hook_ApplyAsync(Apply); CharacterBody.onBodyAwakeGlobal += BodyAwake; installed = true; } private void BodyAwake(CharacterBody body) { if (Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)body.modelLocator) && Object.op_Implicit((Object)(object)body.modelLocator.modelTransform)) { AdvancedSkinModel component = ((Component)body.modelLocator.modelTransform).GetComponent<AdvancedSkinModel>(); if (Object.op_Implicit((Object)(object)component)) { component.BindBody(body); } } } public void Register(SkinDef skin, Mesh mesh) { if (disposed || !Object.op_Implicit((Object)(object)skin) || !AdvancedSkinRegistry.TryGet(mesh, out var _) || !ownedSkins.Add(skin)) { throw new ArgumentException("Register a unique skin with a registered custom mesh"); } } private IEnumerator Apply(orig_ApplyAsync orig, SkinDef skin, GameObject model, List<AssetReferenceT<Material>> materials, List<AssetReferenceT<Mesh>> meshes, AsyncReferenceHandleUnloadType unload) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) AdvancedSkinModel advancedSkinModel = (Object.op_Implicit((Object)(object)model) ? model.GetComponent<AdvancedSkinModel>() : null); SkinRigDriver driver = null; if (ownedSkins.Contains(skin) && !Object.op_Implicit((Object)(object)advancedSkinModel)) { throw new InvalidOperationException("Advanced skin requires its own validated body/display template"); } if (Object.op_Implicit((Object)(object)advancedSkinModel)) { try { driver = advancedSkinModel.Prepare(); } catch (Exception ex) { if (ownedSkins.Contains(skin)) { throw; } Debug.LogError((object)("ECLIPSE_NATIVE_SNAPSHOT_FAILED " + ex)); } } IEnumerator enumerator = orig.Invoke(skin, model, materials, meshes, unload); if (!Object.op_Implicit((Object)(object)driver) || enumerator == null) { return enumerator; } return SkinApplyObservation.Observe(enumerator, delegate { if (Object.op_Implicit((Object)(object)driver)) { driver.SynchronizeSkin(); } }, delegate(Exception ex2) { Debug.LogError((object)("ECLIPSE_SKIN_RECONCILE_FAILED " + ex2)); }); } internal void Remove(SkinDef skin) { ownedSkins.Remove(skin); } public void Dispose() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (!disposed) { SkinDef.ApplyAsync -= new hook_ApplyAsync(Apply); CharacterBody.onBodyAwakeGlobal -= BodyAwake; ownedSkins.Clear(); installed = false; disposed = true; } } } public static class AdvancedSkinApplications { private sealed class Registration : IDisposable { private SkinDef skin; private bool disposed; public Registration(SkinDef skin) { this.skin = skin; } public void Dispose() { if (!disposed) { bridge.Remove(skin); registrations--; if (registrations == 0) { bridge.Dispose(); bridge = null; } skin = null; disposed = true; } } } private static AdvancedSkinApplicationBridge bridge; private static int registrations; public static IDisposable Register(SkinDef skin, Mesh mesh) { if (bridge == null) { bridge = new AdvancedSkinApplicationBridge(); } try { bridge.Register(skin, mesh); } catch { if (registrations == 0) { bridge.Dispose(); bridge = null; } throw; } registrations++; return new Registration(skin); } } public sealed class AdvancedSkinModel : MonoBehaviour { [SerializeField] private SkinnedMeshRenderer target; [SerializeField] private Animator source; [SerializeField] private Mesh nativeMesh; [SerializeField] private Material[] nativeMaterials; [SerializeField] private Transform[] nativeBones; [SerializeField] private Transform nativeRoot; [SerializeField] private Bounds nativeBounds; [SerializeField] private bool nativeOffscreen; [SerializeField] private bool captured; [NonSerialized] private CharacterBody associatedBody; public CharacterBody AssociatedBody { get { if (!Object.op_Implicit((Object)(object)associatedBody) || !Object.op_Implicit((Object)(object)associatedBody.modelLocator) || !((Object)(object)associatedBody.modelLocator.modelTransform == (Object)(object)((Component)this).transform)) { return null; } return associatedBody; } } public bool HasNativeSnapshot => captured; internal void BindBody(CharacterBody body) { if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.modelLocator) || (Object)(object)body.modelLocator.modelTransform != (Object)(object)((Component)this).transform) { throw new ArgumentException("Body does not own this model"); } associatedBody = body; } private bool Owns(Transform value) { if (Object.op_Implicit((Object)(object)value)) { if (!((Object)(object)value == (Object)(object)((Component)this).transform)) { return value.IsChildOf(((Component)this).transform); } return true; } return false; } private void Validate() { if (!captured || !Object.op_Implicit((Object)(object)target) || !Object.op_Implicit((Object)(object)source) || !Object.op_Implicit((Object)(object)nativeMesh) || !Owns(((Component)target).transform) || !Owns(((Component)source).transform) || nativeBones == null || nativeBones.Length != nativeMesh.bindposes.Length || Array.Exists(nativeBones, (Transform b) => !Owns(b)) || (Object.op_Implicit((Object)(object)nativeRoot) && !Owns(nativeRoot)) || nativeMaterials == null || nativeMaterials.Length == 0 || Array.Exists(nativeMaterials, (Material m) => !Object.op_Implicit((Object)(object)m))) { throw new InvalidOperationException("Invalid or cross-instance native skin snapshot"); } } public void CaptureNativeTemplate(SkinnedMeshRenderer renderer, Animator animator, Mesh fallback = null, Material[] fallbackMaterials = null) { //IL_00a3: 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) if (captured) { throw new InvalidOperationException("Native template already captured"); } if (!Object.op_Implicit((Object)(object)renderer) || !Object.op_Implicit((Object)(object)animator) || (!Object.op_Implicit((Object)(object)renderer.sharedMesh) && !Object.op_Implicit((Object)(object)fallback)) || AdvancedSkinRegistry.TryGet(renderer.sharedMesh, out var _)) { throw new ArgumentException("Capture the native template before advanced skin application"); } target = renderer; source = animator; nativeMesh = (Object.op_Implicit((Object)(object)fallback) ? fallback : renderer.sharedMesh); nativeMaterials = fallbackMaterials ?? ((Renderer)renderer).sharedMaterials; nativeBones = renderer.bones; nativeRoot = renderer.rootBone; nativeBounds = ((Renderer)renderer).localBounds; nativeOffscreen = renderer.updateWhenOffscreen; captured = true; try { Validate(); } catch { captured = false; throw; } } public SkinRigDriver Prepare() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) Validate(); SkinRigDriver skinRigDriver = ((Component)this).GetComponent<SkinRigDriver>(); if (!Object.op_Implicit((Object)(object)skinRigDriver)) { skinRigDriver = ((Component)this).gameObject.AddComponent<SkinRigDriver>(); } if (!skinRigDriver.IsConfigured) { skinRigDriver.ConfigureSnapshot(target, nativeMesh, source, nativeBones, nativeRoot, nativeBounds, nativeOffscreen, nativeMaterials); } return skinRigDriver; } private void Awake() { if (captured) { try { Prepare(); } catch (Exception ex) { Debug.LogError((object)("ECLIPSE_NATIVE_SNAPSHOT_FAILED " + ex)); } } } } public sealed class GroundedLandingClock { private bool initialized; private bool wasGrounded; private bool landing; private double last; private double started; private long serial; public long Serial => serial; public double Phase { get; private set; } public bool Observe(bool grounded, bool dead, double now, float duration) { if (!Limits.Finite(now) || !Limits.Finite(duration) || duration <= 0f) { throw new ArgumentException("Landing clock"); } if (!initialized || now < last || now - last > 0.25) { initialized = true; wasGrounded = grounded; landing = false; } else if (!wasGrounded && grounded && !dead) { landing = true; started = now; serial++; } if (!grounded || dead) { landing = false; } wasGrounded = grounded; last = now; Phase = (landing ? Math.Max(0.0, (now - started) / (double)duration) : 0.0); if (Phase >= 1.0) { landing = false; } return landing; } } public sealed class AirPoseTransition { private Pose[] previous; private Pose[] from; private bool[] bypass; private bool initialized; private bool air; private double lastTime; private double changedAt; private bool blending; public void Reset() { initialized = false; blending = false; } public void Apply(Pose[] pose, bool airborne, double now, float seconds, bool[] exempt = null) { if (pose == null || !Limits.Finite(now) || !Limits.Finite(seconds) || seconds <= 0f || (exempt != null && exempt.Length != pose.Length)) { throw new ArgumentException("Air pose transition context"); } if (!initialized || previous.Length != pose.Length || now < lastTime || now - lastTime > 0.25) { previous = (Pose[])pose.Clone(); from = new Pose[pose.Length]; bypass = new bool[pose.Length]; initialized = true; air = airborne; blending = false; } else if (air != airborne) { Array.Copy(previous, from, pose.Length); Array.Clear(bypass, 0, bypass.Length); changedAt = now; air = airborne; blending = true; } if (blending) { float num = (float)Math.Max(0.0, Math.Min(1.0, (now - changedAt) / (double)seconds)); num = num * num * (3f - 2f * num); for (int i = 0; i < pose.Length; i++) { ref bool reference = ref bypass[i]; reference |= exempt != null && exempt[i]; if (!bypass[i]) { pose[i] = Pose.Blend(from[i], pose[i], num); } } if (now - changedAt >= (double)seconds) { blending = false; } } Array.Copy(pose, previous, pose.Length); lastTime = now; } } internal static class CrocoModelActor { public static CharacterBody Resolve(Component member) { if (!Object.op_Implicit((Object)(object)member)) { return null; } CharacterModel componentInParent = member.GetComponentInParent<CharacterModel>(); if (Object.op_Implicit((Object)(object)componentInParent) && Object.op_Implicit((Object)(object)componentInParent.body) && Object.op_Implicit((Object)(object)componentInParent.body.modelLocator) && (Object)(object)componentInParent.body.modelLocator.modelTransform == (Object)(object)((Component)componentInParent).transform) { return componentInParent.body; } AdvancedSkinModel componentInParent2 = member.GetComponentInParent<AdvancedSkinModel>(); if (Object.op_Implicit((Object)(object)componentInParent2) && Object.op_Implicit((Object)(object)componentInParent2.AssociatedBody)) { return componentInParent2.AssociatedBody; } return null; } } public sealed class CrocoModelMotionSource : ISkinMotionSource, IDisposable { private readonly Animator animator; private readonly MotionBank bank; private CharacterBody actor; private CrocoStateMotionSource source; private bool disposed; private readonly bool observeLanding; public CrocoModelMotionSource(Animator animator, MotionBank bank) : this(animator, bank, observeLanding: false) { } public CrocoModelMotionSource(Animator animator, MotionBank bank, bool observeLanding) { if (!Object.op_Implicit((Object)(object)animator)) { throw new ArgumentException("Missing model Animator"); } CrocoStateMotionSource.ValidateBank(bank); this.animator = animator; this.bank = bank; this.observeLanding = observeLanding; } public void Observe(List<NativeMotionCandidate> candidates, HashSet<string> replaceNativeClips) { if (disposed) { return; } CharacterBody val = CrocoModelActor.Resolve((Component)(object)animator); if ((Object)(object)val != (Object)(object)actor) { source?.Dispose(); source = null; actor = val; } if (!Object.op_Implicit((Object)(object)actor)) { return; } if (source == null) { if (((Component)actor).GetComponents<EntityStateMachine>().Length == 0) { return; } source = new CrocoStateMotionSource(actor, bank, observeLanding); } source.Observe(candidates, replaceNativeClips); } public void Dispose() { if (!disposed) { disposed = true; source?.Dispose(); source = null; actor = null; } } } public sealed class CrocoModelGaitTiming : ISkinMotionTiming, IDisposable { private readonly SkinnedMeshRenderer renderer; private readonly MotionBank bank; private readonly GaitCalibration calibration; private CharacterBody actor; private CrocoGaitTiming timing; private bool disposed; public CrocoModelGaitTiming(SkinnedMeshRenderer renderer, MotionBank bank, GaitCalibration calibration) { if (!Object.op_Implicit((Object)(object)renderer) || calibration == null) { throw new ArgumentException("Missing model gait context"); } calibration.Validate(bank); this.renderer = renderer; this.bank = bank; this.calibration = calibration; } public void Retime(MotionLayer[] composed, IEnumerable<NativeMotionCandidate> observed) { if (disposed) { return; } CharacterBody val = CrocoModelActor.Resolve((Component)(object)renderer); if ((Object)(object)val != (Object)(object)actor) { timing?.Dispose(); timing = null; actor = val; } if (Object.op_Implicit((Object)(object)actor) && Object.op_Implicit((Object)(object)actor.characterMotor)) { if (timing == null) { timing = new CrocoGaitTiming(actor, ((Component)renderer).transform, bank, calibration); } timing.Retime(composed, observed); } } public bool OwnsCues(MotionClip clip) { if (!disposed && timing != null) { return timing.OwnsCues(clip); } return false; } public void AdvanceCues(Action<string> emit) { if (!disposed) { timing?.AdvanceCues(emit); } } public void Dispose() { if (!disposed) { disposed = true; timing?.Dispose(); timing = null; actor = null; } } } public sealed class GaitCalibration { public int ReferenceFoot; public readonly Dictionary<string, V3> TravelPerCycle = new Dictionary<string, V3>(); public Cue[] ContactCues; public readonly Dictionary<string, float> CyclesPerWorldMetre = new Dictionary<string, float>(); public double MaximumCyclesPerSecond = 12.0; public double Acceleration; public double EffectiveCadence(IEnumerable<MotionLayer> layers, double worldSpeed) { if (!Limits.Finite(worldSpeed) || worldSpeed < 0.0) { throw new ArgumentException("World gait speed"); } double num = 0.0; foreach (MotionLayer item in layers.OrderBy((MotionLayer l) => l.Priority)) { if (item.BoneMask != null && Array.IndexOf(item.BoneMask, ReferenceFoot) < 0) { continue; } float num2 = 0f; double num3 = 0.0; WeightedMotion[] samples = item.Samples; foreach (WeightedMotion weightedMotion in samples) { if (weightedMotion.Clip.Tracks.Any((Track t) => t.Bone == ReferenceFoot)) { if (!Limits.Finite(weightedMotion.Weight) || weightedMotion.Weight < 0f) { throw new ArgumentException("Gait weight"); } num2 += weightedMotion.Weight; if (CyclesPerWorldMetre.TryGetValue(weightedMotion.Clip.Id, out var value)) { num3 += (double)value * worldSpeed * (double)weightedMotion.Weight; } } } if (!(num2 <= 0f)) { double num5 = Math.Min(1f, num2); if (item.Blend == MotionBlend.Override) { num = num * (1.0 - num5) + num3 * (num5 / (double)num2); } else if (num3 != 0.0) { throw new ArgumentException("Additive gait cadence unsupported"); } } } return num; } public void Validate(MotionBank bank) { if (bank == null || ReferenceFoot < 0 || ReferenceFoot >= bank.Bones.Length || TravelPerCycle.Count == 0 || ContactCues == null || ContactCues.Length == 0) { throw new ArgumentException("Gait calibration"); } new GaitContactClock(ContactCues); if (!Limits.Finite(MaximumCyclesPerSecond) || MaximumCyclesPerSecond <= 0.0 || !Limits.Finite(Acceleration) || Acceleration < 0.0) { throw new ArgumentException("Gait cadence limits"); } if (CyclesPerWorldMetre.Count > 0 && (CyclesPerWorldMetre.Count != TravelPerCycle.Count || CyclesPerWorldMetre.Any((KeyValuePair<string, float> p) => !TravelPerCycle.ContainsKey(p.Key) || !Limits.Finite(p.Value) || p.Value <= 0f))) { throw new ArgumentException("Complete visual gait calibration required"); } foreach (KeyValuePair<string, V3> item in TravelPerCycle) { MotionClip motionClip = bank.Find(item.Key); V3 value = item.Value; if (!motionClip.Loop || !Limits.Finite(value.X) || !Limits.Finite(value.Y) || !Limits.Finite(value.Z) || value.X * value.X + value.Y * value.Y + value.Z * value.Z < 0.0001f) { throw new ArgumentException("Gait stride"); } if (motionClip.Cues.Length != ContactCues.Length || motionClip.Cues.Where((Cue c, int i) => c.Phase != ContactCues[i].Phase || c.Sound != ContactCues[i].Sound).Any()) { throw new ArgumentException("Gaits require a shared contact cue schedule"); } } } public V3 EffectiveTravel(IEnumerable<MotionLayer> layers) { V3 v = default(V3); foreach (MotionLayer item in layers.OrderBy((MotionLayer l) => l.Priority)) { if (item.BoneMask != null && Array.IndexOf(item.BoneMask, ReferenceFoot) < 0) { continue; } float num = 0f; V3 v2 = default(V3); WeightedMotion[] samples = item.Samples; foreach (WeightedMotion weightedMotion in samples) { if (weightedMotion.Clip.Tracks.Any((Track t) => t.Bone == ReferenceFoot)) { if (!Limits.Finite(weightedMotion.Weight) || weightedMotion.Weight < 0f) { throw new ArgumentException("Gait weight"); } num += weightedMotion.Weight; if (TravelPerCycle.TryGetValue(weightedMotion.Clip.Id, out var value)) { v2 += value * weightedMotion.Weight; } } } if (!(num <= 0f)) { float num3 = Math.Min(1f, num); if (item.Blend == MotionBlend.Override) { v = v * (1f - num3) + v2 * (num3 / num); } else if (v2.X != 0f || v2.Y != 0f || v2.Z != 0f) { throw new ArgumentException("Additive gait calibration unsupported"); } } } return v; } } public sealed class GaitContactClock { private readonly CueCursor cursor = new CueCursor(); private readonly MotionClip schedule; public GaitContactClock(Cue[] cues) { if (cues == null || cues.Length == 0 || cues.Length > 128) { throw new ArgumentException("Contact cues"); } float num = -1f; foreach (Cue cue in cues) { if (cue == null || !Limits.Finite(cue.Phase) || cue.Phase < 0f || cue.Phase >= 1f || cue.Phase < num || string.IsNullOrWhiteSpace(cue.Sound)) { throw new ArgumentException("Contact cue schedule"); } num = cue.Phase; } schedule = new MotionClip { Id = "gait_contacts", Loop = true, Cues = cues.Select((Cue c) => new Cue { Phase = c.Phase, Sound = c.Sound }).ToArray() }; } public void Advance(double phase, bool audible, Action<string> emit) { cursor.Advance(schedule, "gait_contacts", phase, delegate(string id) { if (audible) { emit(id); } }); } } public sealed class GaitCycleClock { private bool initialized; private double previous; public double MaximumCyclesPerSecond = 12.0; public double Phase { get; private set; } = 1.0; public double CyclesPerSecond { get; private set; } public double UnmatchedSpeed { get; private set; } public bool RateLimited { get; private set; } private static double Dot(V3 a, V3 b) { return (double)a.X * (double)b.X + (double)a.Y * (double)b.Y + (double)a.Z * (double)b.Z; } public double AdvanceAtRate(double desiredRate, double now, bool grounded, double acceleration = 0.0) { if (!Limits.Finite(desiredRate) || desiredRate < 0.0 || !Limits.Finite(now) || !Limits.Finite(MaximumCyclesPerSecond) || MaximumCyclesPerSecond <= 0.0 || !Limits.Finite(acceleration) || acceleration < 0.0) { throw new ArgumentException("Visual gait clock input"); } double num = (initialized ? (now - previous) : 0.0); initialized = true; previous = now; RateLimited = desiredRate > MaximumCyclesPerSecond; UnmatchedSpeed = 0.0; if (!grounded || desiredRate == 0.0 || num < 0.0 || num > 0.25) { CyclesPerSecond = 0.0; return Phase; } double num2 = Math.Min(MaximumCyclesPerSecond, desiredRate); double cyclesPerSecond = CyclesPerSecond; CyclesPerSecond = ((acceleration == 0.0) ? num2 : Math.Max(cyclesPerSecond - acceleration * num, Math.Min(cyclesPerSecond + acceleration * num, num2))); Phase += num * ((acceleration == 0.0) ? num2 : ((cyclesPerSecond + CyclesPerSecond) * 0.5)); return Phase; } public double Advance(V3 localVelocity, V3 travel, double now, bool grounded) { if (!Limits.Finite(now) || !Limits.Finite(Dot(localVelocity, localVelocity)) || !Limits.Finite(Dot(travel, travel)) || !Limits.Finite(MaximumCyclesPerSecond) || MaximumCyclesPerSecond <= 0.0) { throw new ArgumentException("Gait clock input"); } double num = (initialized ? (now - previous) : 0.0); initialized = true; previous = now; CyclesPerSecond = 0.0; RateLimited = false; UnmatchedSpeed = Math.Sqrt(Dot(localVelocity, localVelocity)); if (!grounded || num < 0.0 || num > 0.25) { return Phase; } double num2 = Dot(travel, travel); if (num2 < 0.0001) { return Phase; } double num3 = Math.Max(0.0, Dot(localVelocity, travel) / num2); CyclesPerSecond = Math.Min(MaximumCyclesPerSecond, num3); RateLimited = num3 > MaximumCyclesPerSecond; V3 v = localVelocity - travel * (float)CyclesPerSecond; UnmatchedSpeed = Math.Sqrt(Dot(v, v)); Phase += num * CyclesPerSecond; return Phase; } } public struct NativeSoundPlayback { public readonly SoundResult Result; public readonly uint PlayingId; public NativeSoundPlayback(SoundResult result, uint playingId = 0u) { if (result == SoundResult.Played != (playingId != 0)) { throw new ArgumentException("Played native sound requires an engine playing ID"); } Result = result; PlayingId = playingId; } } public struct NativeSoundDecision { public readonly bool SuppressNative; public readonly uint PlayingId; public static NativeSoundDecision Forward => new NativeSoundDecision(suppressNative: false); public NativeSoundDecision(bool suppressNative, uint playingId = 0u) { SuppressNative = suppressNative; PlayingId = playingId; } } public sealed class NativeSoundLoop { public string StartEvent; public string StopEvent; public string Cue; } public sealed class NativeSoundRouter { private sealed class LoopState { public string Cue; public bool NativeFallback; public bool HasStart; public readonly HashSet<uint> Playing = new HashSet<uint>(); public readonly HashSet<uint> PendingStops = new HashSet<uint>(); } private readonly Dictionary<string, string> oneShots = new Dictionary<string, string>(StringComparer.Ordinal); private readonly Dictionary<string, LoopState> starts = new Dictionary<string, LoopState>(StringComparer.Ordinal); private readonly Dictionary<string, LoopState> stops = new Dictionary<string, LoopState>(StringComparer.Ordinal); private readonly Func<string, NativeSoundPlayback> play; private readonly Func<uint, bool> stop; public int PendingStopCount { get { int num = 0; foreach (LoopState value in starts.Values) { foreach (uint pendingStop in value.PendingStops) { if (pendingStop != 0) { num++; } } } return num; } } public NativeSoundRouter(IDictionary<string, string> replacements, IEnumerable<NativeSoundLoop> loops, Func<string, NativeSoundPlayback> play, Func<uint, bool> stop) { this.play = play ?? throw new ArgumentNullException("play"); this.stop = stop ?? throw new ArgumentNullException("stop"); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (KeyValuePair<string, string> replacement in replacements) { if (string.IsNullOrWhiteSpace(replacement.Key) || string.IsNullOrWhiteSpace(replacement.Value) || !hashSet.Add(replacement.Key)) { throw new ArgumentException("Invalid native sound mapping"); } oneShots.Add(replacement.Key, replacement.Value); } foreach (NativeSoundLoop loop in loops) { if (loop == null || string.IsNullOrWhiteSpace(loop.StartEvent) || string.IsNullOrWhiteSpace(loop.StopEvent) || string.IsNullOrWhiteSpace(loop.Cue) || !hashSet.Add(loop.StartEvent) || !hashSet.Add(loop.StopEvent)) { throw new ArgumentException("Ambiguous native sound loop pair"); } LoopState value = new LoopState { Cue = loop.Cue }; starts.Add(loop.StartEvent, value); stops.Add(loop.StopEvent, value); } } private static NativeSoundDecision Decision(NativeSoundPlayback result) { return new NativeSoundDecision(result.Result != SoundResult.Unavailable, result.PlayingId); } public NativeSoundDecision Route(string exactEvent) { if (exactEvent == null) { return NativeSoundDecision.Forward; } if (stops.TryGetValue(exactEvent, out var value)) { foreach (uint item in value.Playing) { value.PendingStops.Add(item); } value.Playing.Clear(); value.NativeFallback = false; value.HasStart = false; Retry(value); return NativeSoundDecision.Forward; } if (starts.TryGetValue(exactEvent, out value)) { NativeSoundPlayback result = play(value.Cue); value.HasStart = true; if (result.Result == SoundResult.Unavailable) { value.NativeFallback = true; } else if (result.Result == SoundResult.Played) { value.Playing.Add(result.PlayingId); } return Decision(result); } if (!oneShots.TryGetValue(exactEvent, out var value2)) { return NativeSoundDecision.Forward; } return Decision(play(value2)); } private void Retry(LoopState state) { foreach (uint item in new List<uint>(state.PendingStops)) { try { if (stop(item)) { state.PendingStops.Remove(item); } } catch { } } } public void RetryPendingStops() { foreach (LoopState value in starts.Values) { Retry(value); } } public void StopOwned() { foreach (LoopState value in starts.Values) { foreach (uint item in value.Playing) { value.PendingStops.Add(item); } value.Playing.Clear(); Retry(value); } } } public sealed class OwnedNativeVoices { public sealed class Ticket { internal readonly OwnedNativeVoices Owner; internal int completed; public readonly string Cue; public readonly object Emitter; public uint PlayingId { get; internal set; } public bool StopRequested { get; internal set; } public bool Completed => Volatile.Read(in completed) != 0; internal Ticket(OwnedNativeVoices owner, string cue, object emitter) { Owner = owner; Cue = cue; Emitter = emitter; } public void Complete() { Interlocked.Exchange(ref completed, 1); } } private readonly int capacity; private readonly List<Ticket> tickets = new List<Ticket>(); public int Count { get { Drain(); return tickets.Count; } } public OwnedNativeVoices(int capacity = 8) { if (capacity < 1 || capacity > 32) { throw new ArgumentOutOfRangeException("capacity"); } this.capacity = capacity; } public Ticket Reserve(string cue, int cueLimit, object emitter) { if (string.IsNullOrWhiteSpace(cue) || cueLimit < 1 || cueLimit > 4 || emitter == null) { throw new ArgumentException("Invalid owned voice request"); } Drain(); if (tickets.Count >= capacity || tickets.Count((Ticket t) => t.Cue == cue) >= cueLimit) { return null; } Ticket ticket = new Ticket(this, cue, emitter); tickets.Add(ticket); return ticket; } private void Require(Ticket ticket) { if (ticket == null || ticket.Owner != this || !tickets.Contains(ticket)) { throw new ArgumentException("Voice is not owned by this ledger"); } } public void Commit(Ticket ticket, uint playingId) { Require(ticket); if (playingId == 0 || ticket.PlayingId != 0) { throw new ArgumentException("Invalid native playing ID"); } ticket.PlayingId = playingId; } public void Abort(Ticket ticket) { Require(ticket); ticket.Complete(); Drain(); } public void MarkStopRequested(Ticket ticket) { Require(ticket); if (ticket.PlayingId == 0) { throw new InvalidOperationException("Cannot stop an unposted voice"); } ticket.StopRequested = true; } public void Drain() { tickets.RemoveAll((Ticket t) => t.Completed); } public Ticket[] Active() { Drain(); return tickets.ToArray(); } public void EngineStopped() { foreach (Ticket ticket in tickets) { ticket.Complete(); } tickets.Clear(); } } public sealed class RetryDisposalQueue { private sealed class Entry { public IDisposable Value; public double Next; public int Attempts; } private readonly List<Entry> entries = new List<Entry>(); private bool draining; public int Count => entries.Count; public void Add(IDisposable value) { if (value == null) { return; } foreach (Entry entry in entries) { if (entry.Value == value) { return; } } entries.Add(new Entry { Value = value }); } public void Tick(double now, Action<Exception> failed = null) { if (draining || double.IsNaN(now) || double.IsInfinity(now)) { return; } draining = true; try { Entry[] array = entries.ToArray(); foreach (Entry entry in array) { if (now < entry.Next) { continue; } entry.Attempts++; entry.Next = now + Math.Min(30.0, Math.Pow(2.0, Math.Min(5, entry.Attempts))); try { entry.Value.Dispose(); entries.Remove(entry); } catch (Exception obj) { if (failed != null) { try { failed(obj); } catch { } } } } } finally { draining = false; } } } public static class SkinApplyObservation { private sealed class Observed : IEnumerator, IDisposable { private readonly IEnumerator source; private readonly Action after; private readonly Action<Exception> failed; private Observed nested; private object current; private bool disposed; public object Current => current; public Observed(IEnumerator source, Action after, Action<Exception> failed) { this.source = source; this.after = after; this.failed = failed; } private void Report(Exception ex) { try { failed?.Invoke(ex); } catch { } } private void Notify() { try { after?.Invoke(); } catch (Exception ex) { Report(ex); } } public bool MoveNext() { if (disposed) { return false; } try { if (nested != null) { nested.Dispose(); nested = null; } if (source == null || !source.MoveNext()) { Dispose(); return false; } Notify(); current = source.Current; if (current is IEnumerator enumerator) { nested = new Observed(enumerator, after, failed); current = nested; } return true; } catch { try { Dispose(); } catch (Exception ex) { Report(ex); } throw; } } public void Reset() { throw new NotSupportedException(); } public void Dispose() { if (disposed) { return; } disposed = true; try { nested?.Dispose(); } finally { nested = null; try { (source as IDisposable)?.Dispose(); } finally { current = null; Notify(); } } } } public static IEnumerator Observe(IEnumerator source, Action after, Action<Exception> failed) { return new Observed(source, after, failed); } } public abstract class SkinSoundAsset { } public sealed class UnityClipSoundAsset : SkinSoundAsset { public AudioClip[] Clips; } public sealed class WwiseEventSoundAsset : SkinSoundAsset { public string BankId; public string[] Events; } public sealed class SkinAudioContextUnavailableException : Exception { public SkinAudioContextUnavailableException(string message) : base(message) { } } public sealed class SkinSound { public string Id; public string Anchor; public string Provenance; public SkinSoundAsset Asset; public float Gain = 0.7f; public float Cooldown = 0.15f; public float MinDistance = 3f; public float MaxDistance = 60f; public int MaxVoices = 2; public bool Loop; } public sealed class SkinSoundProfile { public readonly Dictionary<string, SkinSound> Cues = new Dictionary<string, SkinSound>(); public readonly Dictionary<string, string> NativeEventReplacements = new Dictionary<string, string>(); public readonly List<NativeSoundLoop> NativeLoops = new List<NativeSoundLoop>(); public void Validate() { foreach (KeyValuePair<string, SkinSound> cue in Cues) { SkinSound value = cue.Value; if (value == null || cue.Key != value.Id || string.IsNullOrWhiteSpace(value.Anchor) || string.IsNullOrWhiteSpace(value.Provenance) || value.Asset == null || !Limits.Finite(value.Gain) || value.Gain < 0f || value.Gain > 1f || !Limits.Finite(value.Cooldown) || value.Cooldown < 0f || !Limits.Finite(value.MinDistance) || value.MinDistance <= 0f || !Limits.Finite(value.MaxDistance) || value.MaxDistance <= value.MinDistance || value.MaxVoices < 1 || value.MaxVoices > 4) { throw new ArgumentException("Invalid species sound " + cue.Key); } } foreach (KeyValuePair<string, string> nativeEventReplacement in NativeEventReplacements) { if (!Cues.ContainsKey(nativeEventReplacement.Value) || Cues[nativeEventReplacement.Value].Loop) { throw new ArgumentException("Missing/non-one-shot replacement cue " + nativeEventReplacement.Key); } } foreach (NativeSoundLoop nativeLoop in NativeLoops) { if (nativeLoop == null || !Cues.ContainsKey(nativeLoop.Cue) || !Cues[nativeLoop.Cue].Loop) { throw new ArgumentException("Loop replacement requires an explicit loop cue"); } } } } public interface ISkinSoundBackend : IDisposable { bool Available { get; } void Validate(SkinSound sound); SoundResult Play(SkinSound sound, Transform anchor); } public interface ISkinNativeSoundBackend { NativeSoundPlayback PlayNative(SkinSound sound, Transform anchor); bool StopNative(uint playingId); } public interface ISkinSoundBackendUpdate { void Update(); } public sealed class SkinAudioRouter : IDisposable { private readonly SkinSoundProfile profile; private readonly ISkinSoundBackend backend; private readonly Func<string, Transform> anchor; private readonly SoundGate gate = new SoundGate(); private readonly NativeSoundRouter native; private double nativeNow; private bool disposed; public SkinAudioRouter(SkinSoundProfile profile, ISkinSoundBackend backend, Func<string, Transform> anchor) { SkinAudioRouter skinAudioRouter = this; if (profile == null || backend == null || anchor == null) { throw new ArgumentNullException("Audio routing context"); } try { profile.Validate(); foreach (SkinSound value in profile.Cues.Values) { backend.Validate(value); } native = new NativeSoundRouter(profile.NativeEventReplacements, profile.NativeLoops, (string id) => skinAudioRouter.NativeCue(id, skinAudioRouter.nativeNow), (uint id) => ((ISkinNativeSoundBackend)backend).StopNative(id)); } catch { SkinAudioCleanupPump.Release(backend); throw; } this.profile = profile; this.backend = backend; this.anchor = anchor; } private SoundResult Cue(string id, double now) { if (!profile.Cues.TryGetValue(id, out var sound) || sound.Loop || !backend.Available) { return SoundResult.Unavailable; } Transform target = anchor(sound.Anchor); if (!Object.op_Implicit((Object)(object)target)) { return SoundResult.Unavailable; } return gate.Dispatch(id, now, sound.Cooldown, () => backend.Play(sound, target)); } public bool TryCue(string id, double now) { return Cue(id, now) == SoundResult.Played; } private NativeSoundPlayback NativeCue(string id, double now) { ISkinNativeSoundBackend nativeBackend = backend as ISkinNativeSoundBackend; if (nativeBackend == null || !backend.Available || !profile.Cues.TryGetValue(id, out var sound)) { return new NativeSoundPlayback(SoundResult.Unavailable); } Transform target = anchor(sound.Anchor); if (!Object.op_Implicit((Object)(object)target)) { return new NativeSoundPlayback(SoundResult.Unavailable); } NativeSoundPlayback playback = default(NativeSoundPlayback); SoundResult num = gate.Dispatch(id, now, sound.Cooldown, delegate { playback = nativeBackend.PlayNative(sound, target); return playback.Result; }); return new NativeSoundPlayback(num, (num == SoundResult.Played) ? playback.PlayingId : 0u); } public NativeSoundDecision TryReplaceNative(string exactEvent, double now) { nativeNow = now; return native.Route(exactEvent); } public void Update() { (backend as ISkinSoundBackendUpdate)?.Update(); native.RetryPendingStops(); } public void Dispose() { if (!disposed) { native.StopOwned(); backend.Dispose(); gate.Reset(); disposed = true; } } } public sealed class SkinAudioCleanupPump : MonoBehaviour { private static readonly RetryDisposalQueue pending = new RetryDisposalQueue(); private static SkinAudioCleanupPump instance; private static bool quitting; public static void Release(IDisposable backend) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown pending.Add(backend); pending.Tick(Time.unscaledTime, delegate(Exception ex2) { Debug.LogError((object)("ECLIPSE_AUDIO_CLEANUP_RETAINED " + ex2)); }); if (pending.Count > 0 && !Object.op_Implicit((Object)(object)instance) && !quitting) { try { GameObject val = new GameObject("EclipseSpeciesAudioCleanup") { hideFlags = (HideFlags)1 }; Object.DontDestroyOnLoad((Object)val); instance = val.AddComponent<SkinAudioCleanupPump>(); } catch (Exception ex) { Debug.LogError((object)("ECLIPSE_AUDIO_CLEANUP_SCHEDULER_FAILED " + ex)); } } } private void Update() { pending.Tick(Time.unscaledTime, delegate(Exception ex) { Debug.LogError((object)("ECLIPSE_AUDIO_CLEANUP_RETRY " + ex)); }); if (pending.Count == 0) { instance = null; Object.Destroy((Object)(object)((Component)this).gameObject); } } private void OnApplicationQuit() { quitting = true; } } public sealed class UnitySkinSoundBackend : ISkinSoundBackend, IDisposable { private readonly AudioListener listener; private readonly AudioMixerGroup mixer; private readonly List<AudioSource> voices = new List<AudioSource>(); private readonly Dictionary<string, int> variant = new Dictionary<string, int>(); private readonly Dictionary<AudioSource, string> cueIds = new Dictionary<AudioSource, string>(); public bool Available { get { if (Object.op_Implicit((Object)(object)listener) && ((Behaviour)listener).isActiveAndEnabled) { return Object.op_Implicit((Object)(object)mixer); } return false; } } public UnitySkinSoundBackend(AudioListener listener, AudioMixerGroup mixer) { if (!Object.op_Implicit((Object)(object)listener) || !Object.op_Implicit((Object)(object)mixer)) { throw new SkinAudioContextUnavailableException("Explicit listener and game SFX routing required"); } this.listener = listener; this.mixer = mixer; } public void Validate(SkinSound sound) { if (!(sound.Asset is UnityClipSoundAsset { Clips: not null } unityClipSoundAsset) || unityClipSoundAsset.Clips.Length == 0 || Array.Exists(unityClipSoundAsset.Clips, (AudioClip c) => !Object.op_Implicit((Object)(object)c))) { throw new ArgumentException("Unity backend requires loaded clips: " + sound.Id); } } public SoundResult Play(SkinSound s, Transform anchor) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if (!Available) { return SoundResult.Unavailable; } AudioClip[] clips = ((UnityClipSoundAsset)s.Asset).Clips; voices.RemoveAll((AudioSource v) => !Object.op_Implicit((Object)(object)v)); int num = 0; AudioSource val = null; foreach (AudioSource voice in voices) { if (voice.isPlaying && cueIds[voice] == s.Id) { num++; } if (!voice.isPlaying && (Object)(object)val == (Object)null) { val = voice; } } if (num >= s.MaxVoices) { return SoundResult.Limited; } if ((Object)(object)val == (Object)null) { if (voices.Count >= 8) { return SoundResult.Limited; } val = new GameObject("EclipseSpeciesVoice").AddComponent<AudioSource>(); val.playOnAwake = false; voices.Add(val); } ((Component)val).transform.SetParent(anchor, false); ((Component)val).transform.localPosition = Vector3.zero; val.outputAudioMixerGroup = mixer; val.spatialBlend = 1f; val.dopplerLevel = 0f; val.rolloffMode = (AudioRolloffMode)0; val.minDistance = s.MinDistance; val.maxDistance = s.MaxDistance; val.volume = s.Gain; val.pitch = 1f; val.loop = false; variant.TryGetValue(s.Id, out var value); val.clip = clips[value % clips.Length]; variant[s.Id] = (value + 1) % clips.Length; cueIds[val] = s.Id; val.Play(); return SoundResult.Played; } public void Dispose() { foreach (AudioSource voice in voices) { if (Object.op_Implicit((Object)(object)voice)) { voice.Stop(); Object.Destroy((Object)(object)((Component)voice).gameObject); } } voices.Clear(); cueIds.Clear(); variant.Clear(); } } public interface ISkinEffectDriver : IDisposable { void Update(); } public sealed class CosmeticRendererLease : IDisposable { private readonly Dictionary<Renderer, bool> owned = new Dictionary<Renderer, bool>(); public void Hide(GameObject instance) { if (!Object.op_Implicit((Object)(object)instance)) { return; } Renderer[] componentsInChildren = instance.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if (!owned.ContainsKey(val)) { owned.Add(val, val.enabled); } val.enabled = false; } } public void Dispose() { foreach (KeyValuePair<Renderer, bool> item in owned) { if (Object.op_Implicit((Object)(object)item.Key) && !item.Key.enabled) { item.Key.enabled = item.Value; } } owned.Clear(); } } public sealed class CrocoLeapCosmetics : ISkinEffectDriver, IDisposable { private static readonly FieldInfo left = Field("leftFistEffectInstance"); private static readonly FieldInfo right = Field("rightFistEffectInstance"); private readonly SkinnedMeshRenderer renderer; private CharacterBody actor; private EntityStateMachine[] machines; private readonly CosmeticRendererLease lease = new CosmeticRendererLease(); private readonly HashSet<GameObject> previous = new HashSet<GameObject>(); private bool disposed; private static FieldInfo Field(string name) { return typeof(BaseLeap).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } public CrocoLeapCosmetics(SkinnedMeshRenderer renderer) { if (!Object.op_Implicit((Object)(object)renderer) || left == null || right == null || left.FieldType != typeof(GameObject) || right.FieldType != typeof(GameObject)) { throw new ArgumentException("Validated Croco leap effect fields required"); } this.renderer = renderer; } public void Update() { //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } CharacterBody val = CrocoModelActor.Resolve((Component)(object)renderer); if ((Object)(object)val != (Object)(object)actor) { lease.Dispose(); previous.Clear(); actor = val; machines = (Object.op_Implicit((Object)(object)actor) ? ((Component)actor).GetComponents<EntityStateMachine>() : null); } if (!Object.op_Implicit((Object)(object)actor) || !Object.op_Implicit((Object)(object)actor.modelLocator) || !Object.op_Implicit((Object)(object)actor.modelLocator.modelTransform) || machines == null) { return; } HashSet<GameObject> hashSet = new HashSet<GameObject>(); EntityStateMachine[] array = machines; foreach (EntityStateMachine val2 in array) { BaseLeap val3 = (BaseLeap)(Object.op_Implicit((Object)(object)val2) ? /*isinst with value type is only supported in some contexts*/: null); if (val3 == null) { continue; } FieldInfo[] array2 = new FieldInfo[2] { left, right }; for (int j = 0; j < array2.Length; j++) { object? value = array2[j].GetValue(val3); GameObject val4 = (GameObject)((value is GameObject) ? value : null); if (Object.op_Implicit((Object)(object)val4)) { Scene scene = val4.scene; if (((Scene)(ref scene)).IsValid() && val4.transform.IsChildOf(actor.modelLocator.modelTransform)) { hashSet.Add(val4); } } } } if (!previous.SetEquals(hashSet)) { lease.Dispose(); previous.Clear(); foreach (GameObject item in hashSet) { previous.Add(item); } } foreach (GameObject item2 in hashSet) { lease.Hide(item2); } } public void Dispose() { if (!disposed) { lease.Dispose(); previous.Clear(); machines = null; actor = null; disposed = true; } } } public interface ISkinMotionTiming : IDisposable { void Retime(MotionLayer[] composed, IEnumerable<NativeMotionCandidate> observed); bool OwnsCues(MotionClip clip); void AdvanceCues(Action<string> emit); } public sealed class CrocoGaitTiming : ISkinMotionTiming, IDisposable { private readonly CharacterMotor motor; private readonly Transform space; private readonly GaitCalibration calibration; private readonly GaitCycleClock clock = new GaitCycleClock(); private bool warned; private readonly GaitContactClock contacts; private bool audible; public CrocoGaitTiming(CharacterBody body, Transform meshSpace, MotionBank bank, GaitCalibration calibration) { if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.characterMotor) || !Object.op_Implicit((Object)(object)meshSpace) || calibration == null) { throw new ArgumentException("Gait context"); } calibration.Validate(bank); motor = body.characterMotor; space = meshSpace; this.calibration = calibration; contacts = new GaitContactClock(calibration.ContactCues); } public void Retime(MotionLayer[] composed, IEnumerable<NativeMotionCandidate> observed) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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) if (!Object.op_Implicit((Object)(object)motor) || !Object.op_Implicit((Object)(object)space)) { return; } Vector3 val = space.InverseTransformVector(motor.velocity); V3 travel = calibration.EffectiveTravel(composed); clock.MaximumCyclesPerSecond = calibration.MaximumCyclesPerSecond; Vector3 velocity = motor.velocity; double num = Math.Sqrt(velocity.x * velocity.x + velocity.z * velocity.z); double time = ((calibration.CyclesPerWorldMetre.Count == 0) ? clock.Advance(new V3(val.x, val.y, 0f), travel, Time.time, motor.isGrounded) : clock.AdvanceAtRate(calibration.EffectiveCadence(composed, (num < 0.05) ? 0.0 : num), Time.time, motor.isGrounded, calibration.Acceleration)); audible = motor.isGrounded && clock.CyclesPerSecond > 0.0; foreach (WeightedMotion item in composed.SelectMany((MotionLayer l) => l.Samples).Concat(observed.SelectMany((NativeMotionCandidate c) => c.Motion.Samples)).Distinct()) { if (calibration.TravelPerCycle.ContainsKey(item.Clip.Id)) { item.Time = time; item.KnownEntry = false; } } if (clock.RateLimited && !warned) { warned = true; Debug.LogWarning((object)"ECLIPSE_GAIT_RATE_LIMIT: extreme speed exceeds authored cadence; movement unchanged"); } } public bool OwnsCues(MotionClip clip) { return calibration.TravelPerCycle.ContainsKey(clip.Id); } public void AdvanceCues(Action<string> emit) { contacts.Advance(clock.Phase, audible, emit); } public void Dispose() { } } public static class SkinMesh { private static Vector3 V(BinaryReader r) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(Limits.ReadFloat(r), Limits.ReadFloat(r), Limits.ReadFloat(r)); } public static Mesh Read(Stream stream, MotionBank bank) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_039d: Unknown result type (might be due to invalid IL or missing references) //IL_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Unknown result type (might be due to invalid IL or missing references) //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_0460: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_0470: Unknown result type (might be due to invalid IL or missing references) //IL_0478: Unknown result type (might be due to invalid IL or missing references) //IL_0480: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Unknown result type (might be due to invalid IL or missing references) //IL_0490: 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) //IL_04a0: Expected O, but got Unknown //IL_03f7: Unknown result type (might be due to invalid IL or missing references) //IL_03e9: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) if (!stream.CanSeek || stream.Length > 67108864) { throw new InvalidDataException("Mesh stream/size"); } using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); if (binaryReader.ReadUInt32() != 827151173) { throw new InvalidDataException("Unsupported ESM version"); } int num = Limits.Count(binaryReader, 3, 200000); int num2 = Limits.Count(binaryReader, 3, 1200000); int num3 = Limits.Count(binaryReader, 1, 256); if (num2 % 3 != 0 || num3 != bank.Bones.Length) { throw new InvalidDataException("Mesh topology/skeleton mismatch"); } if (stream.Length - stream.Position != (long)num * 64L + (long)num2 * 4L) { throw new InvalidDataException("Mesh length mismatch"); } Vector3[] array = (Vector3[])(object)new Vector3[num]; Vector3[] array2 = (Vector3[])(object)new Vector3[num]; Vector2[] array3 = (Vector2[])(object)new Vector2[num]; BoneWeight[] array4 = (BoneWeight[])(object)new BoneWeight[num]; Vector4[] array5 = (Vector4[])(object)new Vector4[num]; for (int i = 0; i < num; i++) { array[i] = V(binaryReader); array2[i] = V(binaryReader); array3[i] = new Vector2(Limits.ReadFloat(binaryReader), Limits.ReadFloat(binaryReader)); if (Math.Abs(((Vector3)(ref array2[i])).magnitude - 1f) > 0.002f) { throw new InvalidDataException("Nonunit mesh normal"); } int[] array6 = new int[4]; float[] array7 = new float[4]; float num4 = 0f; for (int j = 0; j < 4; j++) { array6[j] = binaryReader.ReadInt32(); if (array6[j] < 0 || array6[j] >= num3) { throw new InvalidDataException("Bone index"); } } for (int k = 0; k < 4; k++) { array7[k] = Limits.ReadFloat(binaryReader); if (array7[k] < 0f || array7[k] > 1f) { throw new InvalidDataException("Bone weight"); } num4 += array7[k]; } if ((double)Math.Abs(num4 - 1f) > 0.0001) { throw new InvalidDataException("Weight sum"); } int num5 = i; BoneWeight val = default(BoneWeight); ((BoneWeight)(ref val)).boneIndex0 = array6[0]; ((BoneWeight)(ref val)).boneIndex1 = array6[1]; ((BoneWeight)(ref val)).boneIndex2 = array6[2]; ((BoneWeight)(ref val)).boneIndex3 = array6[3]; ((BoneWeight)(ref val)).weight0 = array7[0]; ((BoneWeight)(ref val)).weight1 = array7[1]; ((BoneWeight)(ref val)).weight2 = array7[2]; ((BoneWeight)(ref val)).weight3 = array7[3]; array4[num5] = val; Vector3 val2 = Vector3.Cross((Mathf.Abs(array2[i].y) > 0.99f) ? Vector3.right : Vector3.up, array2[i]); Vector3 normalized = ((Vector3)(ref val2)).normalized; array5[i] = new Vector4(normalized.x, normalized.y, normalized.z, 1f); } int[] array8 = new int[num2]; for (int l = 0; l < num2; l++) { array8[l] = binaryReader.ReadInt32(); if (array8[l] < 0 || array8[l] >= num) { throw new InvalidDataException("Triangle index"); } } Matrix4x4[] array9 = (Matrix4x4[])(object)new Matrix4x4[num3]; Matrix4x4[] array10 = (Matrix4x4[])(object)new Matrix4x4[num3]; for (int m = 0; m < num3; m++) { Bone bone = bank.Bones[m]; Pose rest = bone.Rest; Matrix4x4 val3 = Matrix4x4.TRS(new Vector3(rest.Position.X, rest.Position.Y, rest.Position.Z), new Quaternion(rest.Rotation.X, rest.Rotation.Y, rest.Rotation.Z, rest.Rotation.W), new Vector3(rest.Scale.X, rest.Scale.Y, rest.Scale.Z)); array9[m] = ((bone.Parent < 0) ? val3 : (array9[bone.Parent] * val3)); if (Mathf.Abs(((Matrix4x4)(ref array9[m])).determinant) < 1E-08f) { throw new InvalidDataException("Singular bind pose"); } array10[m] = ((Matrix4x4)(ref array9[m])).inverse; } Mesh val4 = new Mesh { name = bank.SkinId, indexFormat = (IndexFormat)1, vertices = array, normals = array2, uv = array3, boneWeights = array4, bindposes = array10, tangents = array5, triangles = array8 }; val4.RecalculateBounds(); return val4; } } public struct V3 { public float X; public float Y; public float Z; public V3(float x, float y, float z) { X = x; Y = y; Z = z; } public static V3 operator +(V3 a, V3 b) { return new V3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); } public static V3 operator -(V3 a, V3 b) { return new V3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); } public static V3 operator *(V3 a, float t) { return new V3(a.X * t, a.Y * t, a.Z * t); } public static V3 Lerp(V3 a, V3 b, float t) { return a + (b - a) * t; } } public struct Q4 { public float X; public float Y; public float Z; public float W; public static Q4 Identity => new Q4(0f, 0f, 0f, 1f); public Q4 Inverse => new Q4(0f - X, 0f - Y, 0f - Z, W); public Q4(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } public float Dot(Q4 b) { return X * b.X + Y * b.Y + Z * b.Z + W * b.W; } public Q4 Normalized() { float num = (float)Math.Sqrt(Dot(this)); if (num < 1E-08f || !Limits.Finite(num)) { throw new InvalidDataException("Invalid quaternion"); } return new Q4(X / num, Y / num, Z / num, W / num); } public static Q4 operator *(Q4 a, Q4 b) { return new Q4(a.W * b.X + a.X * b.W + a.Y * b.Z - a.Z * b.Y, a.W * b.Y - a.X * b.Z + a.Y * b.W + a.Z * b.X, a.W * b.Z + a.X * b.Y - a.Y * b.X + a.Z * b.W, a.W * b.W - a.X * b.X - a.Y * b.Y - a.Z * b.Z); } public static Q4 Blend(Q4 a, Q4 b, float t) { float num = ((!(a.Dot(b) < 0f)) ? 1 : (-1)); return new Q4(a.X * (1f - t) + b.X * t * num, a.Y * (1f - t) + b.Y * t * num, a.Z * (1f - t) + b.Z * t * num, a.W * (1f - t) + b.W * t * num).Normalized(); } } public struct Pose { public V3 Position; public V3 Scale; public Q4 Rotation; public Pose(V3 p, Q4 r, V3 s) { Position = p; Rotation = r; Scale = s; } public static Pose Blend(Pose a, Pose b, float t) { return new Pose(V3.Lerp(a.Position, b.Position, t), Q4.Blend(a.Rotation, b.Rotation, t), V3.Lerp(a.Scale, b.Scale, t)); } public static Pose Add(Pose basis, Pose value, Pose rest, float weight) { V3 v = new V3(value.Scale.X / rest.Scale.X, value.Scale.Y / rest.Scale.Y, value.Scale.Z / rest.Scale.Z); return new Pose(basis.Position + (value.Position - rest.Position) * weight, (basis.Rotation * Q4.Blend(Q4.Identity, rest.Rotation.Inverse * value.Rotation, weight)).Normalized(), new V3(basis.Scale.X * (1f + (v.X - 1f) * weight), basis.Scale.Y * (1f + (v.Y - 1f) * weight), basis.Scale.Z * (1f + (v.Z - 1f) * weight))); } } public sealed class Bone { public string Name; public int Parent; public Pose Rest; } public sealed class Track { public int Bone; public Pose[] Frames; } public sealed class Cue { public float Phase; public string Sound; } public sealed class MotionClip { public string Id; public float Duration; public bool Loop; public int FrameCount; public Track[] Tracks; public Cue[] Cues; public Pose Sample(Track track, double normalizedTime) { if (!Limits.Finite(normalizedTime)) { throw new InvalidDataException("Invalid sample time"); } double num = (Loop ? (normalizedTime - Math.Floor(normalizedTime)) : Math.Max(0.0, Math.Min(1.0, normalizedTime))) * (double)(FrameCount - 1); int num2 = Math.Min(FrameCount - 1, (int)num); int num3 = Math.Min(num2 + 1, FrameCount - 1); return Pose.Blend(track.Frames[num2], track.Frames[num3], (float)(num - (double)num2)); } } public enum MotionBlend : byte { Override, Additive } public enum MotionClock : byte { NativeState, Elapsed, Parameter } public sealed class MotionOccurrence { public long Serial; public int Hash; public double ObservedAt; public double EntryPhase; public double LastPhase; public double Elapsed(double now, float duration) { return EntryPhase + Math.Max(0.0, now - ObservedAt) / (double)duration; } } public sealed class ObservedStateClock { private object previous; private double age; private double lastObserved; private long serial; private bool initialized; public bool BeganSinceLastObservation { get; private set; } public long Observe(object state, double stateAge, double now = 0.0) { if (!Limits.Finite(stateAge) || stateAge < 0.0 || !Limits.Finite(now)) { throw new ArgumentException("State age"); } bool flag = state != previous || stateAge < age - 0.0001; double num = now - lastObserved; BeganSinceLastObservation = flag && state != null && initialized && num >= 0.0 && num <= 0.25 && stateAge <= num + 0.05; if (flag) { serial++; } previous = state; age = stateAge; lastObserved = now; initialized = true; return serial; } } public sealed class ObservedMotionChannel { private string token; private double changedAt; private float fade; private bool initialized; private WeightedMotion incoming; private WeightedMotion[] outgoing = new WeightedMotion[0]; private static WeightedMotion Copy(WeightedMotion source, float weight) { return new WeightedMotion { Clip = source.Clip, Time = source.Time, Token = source.Token, Weight = weight, OriginGroup = source.OriginGroup, OriginPriority = source.OriginPriority, KnownEntry = source.KnownEntry }; } private WeightedMotion[] Evaluate(double now) { float num = ((fade == 0f) ? 1f : ((float)Math.Max(0.0, Math.Min(1.0, (now - changedAt) / (double)fade)))); List<WeightedMotion> list = new List<WeightedMotion>(); WeightedMotion[] array = outgoing; foreach (WeightedMotion weightedMotion in array) { float num2 = weightedMotion.Weight * (1f - num); if (num2 > 0f) { list.Add(Copy(weightedMotion, num2)); } } if (incoming != null) { list.Add(Copy(incoming, num)); } return list.ToArray(); } private static WeightedMotion[] Bounded(WeightedMotion[] history) { float num = history.Sum((WeightedMotion s) => s.Weight); WeightedMotion[] array = (from s in history where s.Weight > 1E-06f orderby s.Weight descending select s).Take(32).ToArray(); float num2 = array.Sum((WeightedMotion s) => s.Weight); if (num2 > 0f) { WeightedMotion[] array2 = array; for (int num3 = 0; num3 < array2.Length; num3++) { array2[num3].Weight *= num / num2; } } return array; } public WeightedMotion[] Sample(MotionClip clip, string occurrence, double phase, double now, float fadeSeconds, string group = null, int priority = 0, bool entered = false) { if (!Limits.Finite(now) || !Limits.Finite(phase) || phase < 0.0 || !Limits.Finite(fadeSeconds) || fadeSeconds < 0f) { throw new ArgumentException("Observed motion time"); } if (!initialized) { changedAt = now - (double)fadeSeconds; token = occurrence; initialized = true; } else if (token != occurrence) { outgoing = Bounded(Evaluate(now)); changedAt = now; token = occurrence; } fade = fadeSeconds; incoming = ((clip == null) ? null : new WeightedMotion { Clip = clip, Time = phase, Token = occurrence, OriginGroup = group, OriginPriority = priority, KnownEntry = entered }); WeightedMotion[] result = Evaluate(now); if (now - changedAt >= (double)fade) { outgoing = new WeightedMotion[0]; } return result; } } public sealed class LayerMotionClock { private long serial; private bool transitioning; private double lastNow; public MotionOccurrence Current { get; private set; } public MotionOccurrence Next { get; private set; } private MotionOccurrence New(int hash, double phase, double now) { return new MotionOccurrence { Serial = ++serial, Hash = hash, ObservedAt = now, EntryPhase = phase - Math.Floor(phase), LastPhase = phase }; } public void Observe(int currentHash, double currentPhase, bool transition, int nextHash, double nextPhase, double now) { if (!Limits.Finite(currentPhase) || !Limits.Finite(nextPhase) || !Limits.Finite(now)) { throw new ArgumentException("State clock"); } if (now < lastNow) { Current = null; Next = null; transitioning = false; } if (Next != null && Next.Hash == currentHash && (Current == null || Current.Hash != currentHash || (transitioning && !transition && Math.Abs(currentPhase - Next.LastPhase) < Math.Abs(currentPhase - Current.LastPhase)))) { Current = Next; Next = null; } else if (Current == null || Current.Hash != currentHash || currentPhase < Current.LastPhase - 0.0001) { Current = New(currentHash, currentPhase, now); } Current.LastPhase = currentPhase; if (transition) { if (Next == null || Next.Hash != nextHash || nextPhase < Next.LastPhase - 0.0001) { Next = New(nextHash, nextPhase, now); } Next.LastPhase = nextPhase; } else { Next = null; } transitioning = transition; lastNow = now; } } public sealed class MotionBinding { public string NativeClip; public string Clip; public string Layer; public string Group; public string Parameter; public int Priority; public MotionBlend Blend; public MotionClock Clock; } public static class Limits { public static bool Finite(double x) { if (!double.IsNaN(x)) { return !double.IsInfinity(x); } return false; } public static float ReadFloat(BinaryReader r) { float num = r.ReadSingle(); if (!Finite(num)) { throw new InvalidDataException("Nonfinite value"); } return num; } public static int Count(BinaryReader r, int min, int max) { int num = r.ReadInt32(); if (num < min || num > max) { throw new InvalidDataException("Invalid count " + num); } return num; } public static string Text(BinaryReader r) { int num = Count(r, 1, 256); byte[] array = r.ReadBytes(num); if (array.Length != num) { throw new EndOfStreamException(); } string text = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(array); if (text.Any(char.IsControl)) { throw new InvalidDataException("Control character"); } return text; } private static V3 Vec(BinaryReader r) { return new V3(ReadFloat(r), ReadFloat(r), ReadFloat(r)); } public static Pose ReadPose(BinaryReader r) { V3 p = Vec(r); Q4 b = new Q4(ReadFloat(r), ReadFloat(r), ReadFloat(r), ReadFloat(r)); V3 s = Vec(r); if ((double)Math.Abs(b.Dot(b) - 1f) > 0.002 || s.X <= 0f || s.Y <= 0f || s.Z <= 0f) { throw new InvalidDataException("Invalid rest/frame transform"); } return new Pose(p, b.Normalized(), s); } } public sealed class MotionBank { public string SkinId; public Bone[] Bones; public MotionClip[] Clips; public MotionBinding[] Bindings; public V3 BoundsCenter; public V3 BoundsSize; public MotionClip Find(string id) { return Clips.First((MotionClip c) => c.Id == id); } public static MotionBank Read(Stream stream) { if (!stream.CanSeek || stream.Length > 67108864) { throw new InvalidDataException("Motion bank size/stream"); } using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); if (binaryReader.ReadUInt32() != 827020101 || binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unsupported ESK version"); } MotionBank motionBank = new MotionBank { SkinId = Limits.Text(binaryReader) }; motionBank.BoundsCenter = new V3(Limits.ReadFloat(binaryReader), Limits.ReadFloat(binaryReader), Limits.ReadFloat(binaryReader)); motionBank.BoundsSize = new V3(Limits.ReadFloat(binaryReader), Limits.ReadFloat(binaryReader), Limits.ReadFloat(binaryReader)); if (motionBank.BoundsSize.X <= 0f || motionBank.BoundsSize.Y <= 0f || motionBank.BoundsSize.Z <= 0f) { throw new InvalidDataException("Invalid bounds"); } motionBank.Bones = new Bone[Limits.Count(binaryReader, 1, 256)]; HashSet<string> hashSet = new HashSet<string>(); int num = 0; for (int i = 0; i < motionBank.Bones.Length; i++) { string text = Limits.Text(binaryReader); int num2 = binaryReader.ReadInt32(); if (!hashSet.Add(text) || num2 < -1 || num2 >= i) { throw new InvalidDataException("Bone order/name"); } if (num2 == -1) { num++; } motionBank.Bones[i] = new Bone { Name = text, Parent = num2, Rest = Limits.ReadPose(binaryReader) }; } if (num != 1) { throw new InvalidDataException("Exactly one visual root required"); } motionBank.Clips = new MotionClip[Limits.Count(binaryReader, 1, 256)]; hashSet.Clear(); long num3 = 0L; for (int j = 0; j < motionBank.Clips.Length; j++) { MotionClip motionClip = new MotionClip { Id = Limits.Text(binaryReader), Duration = Limits.ReadFloat(binaryReader) }; byte b = binaryReader.ReadByte(); if (!hashSet.Add(motionClip.Id) || motionClip.Duration <= 0f || motionClip.Duration > 120f || b > 1) { throw new InvalidDataException("Clip metadata"); } motionClip.Loop = b == 1; motionClip.FrameCount = Limits.Count(binaryReader, 2, 3601); motionClip.Tracks = new Track[Limits.Count(binaryReader, 1, motionBank.Bones.Length)]; HashSet<int> hashSet2 = new HashSet<int>(); num3 += (long)motionClip.FrameCount * (long)motionClip.Tracks.Length; if (num3 > 1000000) { throw new InvalidDataException("Motion bank allocation budget"); } for (int k = 0; k < motionClip.Tracks.Length; k++) { int num4 = binaryReader.ReadInt32(); if (num4 < 0 || num4 >= motionBank.Bones.Length || !hashSet2.Add(num4)) { throw new InvalidDataException("Track bone"); } Track track = new Track { Bone = num4, Frames = new Pose[motionClip.FrameCount] }; for (int l = 0; l < motionClip.FrameCount; l++) { track.Frames[l] = Limits.ReadPose(binaryReader); } motionClip.Tracks[k] = track; } motionClip.Cues = new Cue[Limits.Count(binaryReader, 0, 128)]; float num5 = -1f; for (int m = 0; m < motionClip.Cues.Length; m++) { float num6 = Limits.ReadFloat(binaryReader); if (num6 < 0f || num6 >= 1f || num6 < num5) { throw new InvalidDataException("Cue phase/order"); } num5 = num6; motionClip.Cues[m] = new Cue { Phase = num6, Sound = Limits.Text(binaryReader) }; } motionBank.Clips[j] = motionClip; } motionBank.Bindings = new MotionBinding[Limits.Count(binaryReader, 1, 1024)]; HashSet<string> hashSet3 = new HashSet<string>(); for (int n = 0; n < motionBank.Bindings.Length; n++) { MotionBinding motionBinding = new MotionBinding { NativeClip = Limits.Text(binaryReader), Clip = Limits.Text(binaryReader), Layer = Limits.Text(binaryReader), Group = Limits.Text(binaryReader), Priority = binaryReader.ReadInt32(), Blend = (MotionBlend)binaryReader.ReadByte(), Clock = (MotionClock)binaryReader.ReadByte(), Parameter = Limits.Text(binaryReader) }; if (!hashSet.Contains(motionBinding.Clip) || motionBinding.Priority < 0 || motionBinding.Priority > 1000 || (int)motionBinding.Blend > 1 || (int)motionBinding.Clock > 2 || !hashSet3.Add(motionBinding.Layer + "\n" + motionBinding.NativeClip)) { throw new InvalidDataException("Binding metadata"); } motionBank.Bindings[n] = motionBinding; } if (stream.Position != stream.Length) { throw new InvalidDataException("Trailing motion data"); } return motionBank; } } public sealed class WeightedMotion { public MotionClip Clip; public double Time; public float Weight; public string Token; public string OriginGroup; public int OriginPriority; public bool KnownEntry; } public sealed class MotionLayer { public string Group; public int Priority; public MotionBlend Blend; public WeightedMotion[] Samples; public int[] BoneMask; } public sealed class NativeMotionCandidate { public int SourceLayer; public int SourceRank; public MotionLayer Motion; } public static class NativeMotionComposition { public static NativeMotionCandidate[] ResolveSources(IEnumerable<NativeMotionCandidate> candidates) { return (from c in candidates where c.Motion.Samples.Any((WeightedMotion s) => s.Weight > 0f) group c by c.Motion.Group).SelectMany(delegate(IGrouping<string, NativeMotionCandidate> g) { NativeMotionCandidate selected = (from c in g orderby c.SourceRank descending, c.SourceLayer descending select c).First(); return g.Where((NativeMotionCandidate c) => c.SourceRank == selected.SourceRank && c.SourceLayer == selected.SourceLayer); }).ToArray(); } public static MotionLayer[] ComposeSources(IEnumerable<NativeMotionCandidate> chosen) { List<MotionLayer> list = new List<MotionLayer>(); foreach (var item in from c in chosen group c by new { c.SourceLayer, c.Motion.Blend }) { Dictionary<int, int> priorities = new Dictionary<int, int>(); Dictionary<int, List<int>> dictionary = new Dictionary<int, List<int>>(); List<WeightedMotion> samples = new List<WeightedMotion>(); foreach (NativeMotionCandidate item2 in item) { foreach (WeightedMotion item3 in item2.Motion.Samples.Where((WeightedMotion s) => s.Weight > 0f)) { int count = samples.Count; samples.Add(item3); Track[] tracks = item3.Clip.Tracks; foreach (Track track in tracks) { if (item2.Motion.BoneMask == null || Array.IndexOf(item2.Motion.BoneMask, track.Bone) >= 0) { if (!priorities.TryGetValue(track.Bone, out var value) || item2.Motion.Priority > value) { priorities[track.Bone] = item2.Motion.Priority; } if (!dictionary.TryGetValue(track.Bone, out var value2)) { value2 = (dictionary[track.Bone] = new List<int>()); } value2.Add(count); } } } } foreach (IGrouping<string, KeyValuePair<int, List<int>>> item4 in from p in dictionary group p by priorities[p.Key] + ":" + string.Join(",", p.Value)) { int key = item4.First().Key; list.Add(new MotionLayer { Group = "native-layer-" + item.Key.SourceLayer + "-" + item4.Key, Blend = item.Key.Blend, Priority = priorities[key], BoneMask = item4.Select((KeyValuePair<int, List<int>> p) => p.Key).ToArray(), Samples = dictionary[key].Select((int i) => samples[i]).ToArray() }); } } return list.ToArray(); } } public static class ObservedMotionComposition { private static Dictionary<int, float> Weights(IEnumerable<NativeMotionCandidate> candidates) { Dictionary<int, float> dictionary = new Dictionary<int, float>(); foreach (NativeMotionCandidate candidate in candidates) { WeightedMotion[] samples = candidate.Motion.Samples; foreach (WeightedMotion weightedMotion in samples) { Track[] tracks = weightedMotion.Clip.Tracks; foreach (Track track in tracks) { if (candidate.Motion.BoneMask == null || Array.IndexOf(candidate.Motion.BoneMask, track.Bone) >= 0) { dictionary.TryGetValue(track.Bone, out var value); dictionary[track.Bone] = value + weightedMotion.Weight; } } } } return dictionary; } public static NativeMotionCandidate[] Choose(List<NativeMotionCandidate> native, NativeMotionCandidate[] observed, HashSet<string> replacedClips) { List<NativeMotionCandidate> list = new List<NativeMotionCandidate>(); foreach (var item in from c in native group c by new { c.SourceLayer, c.Motion.Blend }) { Dictionary<int, float> before = Weights(item); foreach (NativeMotionCandidate item2 in item) { item2.Motion.Samples = item2.Motion.Samples.Where((WeightedMotion s) => !replacedClips.Contains(s.Clip.Id)).ToArray(); } Dictionary<int, float> after = Weights(item); foreach (NativeMotionCandidate candidate in item) { foreach (IGrouping<float, int> item3 in from b in candidate.Motion.Samples.SelectMany((WeightedMotion s) => s.Clip.Tracks.Select((Track t) => t.Bone)).Distinct() where candidate.Motion.BoneMask == null || Array.IndexOf(candidate.Motion.BoneMask, b) >= 0 group b by (!(after[b] > 0f)) ? 1f : (before[b] / after[b])) { float factor = item3.Key; list.Add(new NativeMotionCandidate { SourceLayer = candidate.SourceLayer, SourceRank = candidate.SourceRank, Motion = new MotionLayer { Group = candidate.Motion.Group, Priority = candidate.Motion.Priority, Blend = candidate.Motion.Blend, BoneMask = item3.ToArray(), Samples = candidate.Motion.Samples.Select((WeightedMotion s) => new WeightedMotion { Clip = s.Clip, Time = s.Time, Weight = Math.Min(1f, s.Weight * factor), Token = s.Token, KnownEntry = s.KnownEntry }).ToArray() } }); } } } native.Clear(); native.AddRange(list); return NativeMotionComposition.ResolveSources(native).Concat(observed).ToArray(); } } public sealed class PoseMixer { private readonly MotionBank bank; private readonly Pose[] scratch; private readonly float[] weights; private readonly MotionClip fallback; public PoseMixer(MotionBank bank, string fallbackClip = null) { this.bank = bank; scratch = new Pose[bank.Bones.Length]; weights = new float[scratch.Length]; fallback = ((fallbackClip == null) ? null : bank.Find(fallbackClip)); } public void Evaluate(IEnumerable<MotionLayer> layers, Pose[] destination) { if (destination.Length != bank.Bones.Length) { throw new ArgumentException("Pose size"); } for (int i = 0; i < destination.Length; i++) { destination[i] = bank.Bones[i].Rest; } if (fallback != null) { Track[] tracks = fallback.Tracks; foreach (Track track in tracks) { destination[track.Bone] = fallback.Sample(track, 0.0); } } foreach (MotionLayer item in layers.OrderBy((MotionLayer l) => l.Priority)) { Array.Clear(weights, 0, weights.Length); WeightedMotion[] samples = item.Samples; foreach (WeightedMotion weightedMotion in samples) { if (!Limits.Finite(weightedMotion.Weight) || weightedMotion.Weight < 0f || weightedMotion.Weight > 1f) { throw new InvalidDataException("Blend weight"); } if (weightedMotion.Weight == 0f) { continue; } Track[] tracks = weightedMotion.Clip.Tracks; foreach (Track track2 in tracks) { int bone = track2.Bone; if (item.BoneMask == null || Array.IndexOf(item.BoneMask, bone) >= 0) { float num2 = weights[bone] + weightedMotion.Weight; Pose pose = weightedMotion.Clip.Sample(track2, weightedMotion.Time); scratch[bone] = ((weights[bone] == 0f) ? pose : Pose.Blend(scratch[bone], pose, weightedMotion.Weight / num2)); weights[bone] = num2; } } } for (int num3 = 0; num3 < weights.Length; num3++) { if (weights[num3] > 0f) { float num4 = Math.Min(1f, weights[num3]); destination[num3] = ((item.Blend == MotionBlend.Additive) ? Pose.Add(destination[num3], scratch[num3], bank.Bones[num3].Rest, num4) : Pose.Blend(destination[num3], scratch[num3], num4)); } } } } } public sealed class CueCursor { private string token; private double previous; public void Reset() { token = null; previous = 0.0; } public void Advance(MotionClip clip, string stateToken, double time, Action<string> emit, bool knownEntry = false) { if (!Limits.Finite(time) || time < 0.0) { throw new InvalidDataException("Audio clock"); } double num = (clip.Loop ? time : Math.Min(1.0, time)); if (token != stateToken || num < previous) { token = stateToken; previous = ((num <= 0.15 || (knownEntry && time <= 1.0)) ? (-1E-07) : num); } if (num - previous > 1.0) { previous = num; return; } long num2 = Math.Max(0L, (long)Math.Floor(previous)); long num3 = (clip.Loop ? ((long)Math.Floor(num)) : 0); for (long num4 = num2; num4 <= num3; num4++) { Cue[] cues = clip.Cues; foreach (Cue cue in cues) { double num5 = (float)num4 + cue.Phase; if (num5 > previous && num5 <= num) { emit(cue.Sound); } } } previous = num; } } public sealed class MotionCueRouter { private sealed class Stream { public readonly CueCursor Cursor = new CueCursor(); public readonly List<string> Pending = new List<string>(); public double Time; public double ZeroSince; public bool Zero; public bool Expired; } private readonly Dictionary<string, Stream> streams = new Dictionary<string, Stream>(); public void Clear() { streams.Clear(); } public void Advance(IEnumerable<NativeMotionCandidate> observed, IEnumerable<NativeMotionCandidate> chosen, Action<string> emit, Func<MotionClip, bool> suppress = null) { HashSet<NativeMotionCandidate> hashSet = new HashSet<NativeMotionCandidate>(chosen); HashSet<string> hashSet2 = new HashSet<string>(); foreach (NativeMotionCandidate item in observed) { WeightedMotion[] samples = item.Motion.Samples; foreach (WeightedMotion sample in samples) { if (suppress != null && suppress(sample.Clip)) { continue; } string text = item.Motion.Group + ":" + sample.Token; hashSet2.Add(text); if (!streams.TryGetValue(text, out var stream)) { streams[text] = (stream = new Stream()); } if (sample.Time < stream.Time || sample.Time - stream.Time > 1.0) { stream.Pending.Clear(); } if (sample.Weight == 0f && !stream.Zero) { stream.Zero = true; stream.ZeroSince = sample.Time; stream.Expired = false; } if (stream.Zero && sample.Time - stream.ZeroSince > 1.0) { stream.Pending.Clear(); stream.Expired = true; } bool audible = hashSet.Contains(item) && sample.Weight > 0f; if (audible) { foreach (string item2 in stream.Pending) { emit(item2); } stream.Pending.Clear(); } else if (sample.Weight > 0f) { stream.Pending.Clear(); } stream.Cursor.Advance(sample.Clip, sample.Token, sample.Time, delegate(string id) { if (audible) { emit(id); } else if (sample.Weight == 0f && !stream.Expired && stream.Pending.Count < 128) { stream.Pending.Add(id); } }, sample.KnownEntry); if (sample.Weight > 0f) { stream.Zero = false; stream.Expired = false; } stream.Time = sample.Time; } } string[] array = streams.Keys.Except(hashSet2).ToArray(); foreach (string key in array) { streams.Remove(key); } } } public sealed class SoundGate { private readonly Dictionary<string, double> last = new Dictionary<string, double>(); public bool Admit(string id, double now, double cooldown) { if (!Limits.Finite(now) || !Limits.Finite(cooldown) || cooldown < 0.0) { throw new ArgumentException("Sound timing"); } if (last.TryGetValue(id, out var value) && now - value < cooldown) { return false; } last[id] = now; return true; } public SoundResult Dispatch(string id, double now, double cooldown, Func<SoundResult> play) { if (!Limits.Finite(now) || !Limits.Finite(cooldown) || cooldown < 0.0) { throw new ArgumentException("Sound timing"); } if (last.TryGetValue(id, out var value) && now >= value && now - value < cooldown) { return SoundResult.Limited; } SoundResult soundResult = play(); if (soundResult == SoundResult.Played || soundResult == SoundResult.Limited) { last[id] = now; } return soundResult; } public void Reset() { last.Clear(); } } public enum SoundResult { Unavailable, Played, Limited } public sealed class AdvancedSkinDefinition { public Mesh Mesh; public MotionBank Motion; public string FallbackClip = "idle"; public Func<Func<string, Transform>, SkinAudioRouter> CreateAudio; public Func<Animator, MotionBank, ISkinMotionSource> CreateMotionSource; public float AirTransitionSeconds; public Func<SkinnedMeshRenderer, ISkinEffectDriver> CreateEffects; public Func<SkinnedMeshRenderer, MotionBank, ISkinMotionTiming> CreateMotionTiming; public readonly Dictionary<string, string[]> NativeLayerPreference = new Dictionary<string, string[]>(); } public static class AdvancedSkinRegistry { private static readonly Dictionary<Mesh, AdvancedSkinDefinition> definitions = new Dictionary<Mesh, AdvancedSkinDefinition>(); public static void Register(AdvancedSkinDefinition definition) { if (definition == null || !Object.op_Implicit((Object)(object)definition.Mesh) || definition.Motion == null || definition.Mesh.bindposes.Length != definition.Motion.Bones.Length) { throw new ArgumentException("Incomplete advanced skin"); } definition.Motion.Find(definition.FallbackClip); if (definitions.ContainsKey(definition.Mesh)) { throw new ArgumentException("Mesh already registered"); } definitions.Add(definition.Mesh, definition); } public static bool TryGet(Mesh mesh, out AdvancedSkinDefinition definition) { definition = null; if (Object.op_Implicit((Object)(object)mesh)) { return definitions.TryGetValue(mesh, out definition); } return false; } } [DefaultExecutionOrder(32000)] public sealed class SkinRigDriver : MonoBehaviour { [SerializeField] private SkinnedMeshRenderer target; [SerializeField] private Mesh stockMesh; [SerializeField] private Animator animator; private Transform[] stockBones; private Transform[] bones; private Transform stockRoot; private Bounds stockBounds; private bool stockOffscreen; private Material[] stockMaterials; private GameObject container; private AdvancedSkinDefinition active; private Mesh failedMesh; private PoseMixer mixer; private Pose[] pose; private bool configured; private readonly AirPoseTransition airTransition = new AirPoseTransition(); private bool[] transitionExempt; private SkinAudioRouter audio; private bool audioAttempted; private int audioAttempts; private float nextAudioAttempt; private ISkinMotionSource motionSource; private ISkinEffectDriver effects; private bool effectsAttempted; private ISkinMotionTiming motionTiming; private bool motionSourcesInitialized; private readonly Dictionary<int, LayerMotionClock> stateClocks = new Dictionary<int, LayerMotionClock>(); private readonly List<AnimatorClipInfo> current = new List<AnimatorClipInfo>(); private readonly List<AnimatorClipInfo> next = new List<AnimatorClipInfo>(); private readonly MotionCueRouter cues = new MotionCueRouter(); private readonly HashSet<string> unknown = new HashSet<string>(); public bool IsConfigured => configured; public void Configure(SkinnedMeshRenderer renderer, Mesh fallback, Animator source, Material[] fallbackMaterials = null) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (active != null) { throw new InvalidOperationException("Cannot configure active rig"); } if (!Object.op_Implicit((Object)(object)renderer) || !Object.op_Implicit((Object)(object)fallback) || !Object.op_Implicit((Object)(object)source)) { throw new ArgumentException("Missing native rig context"); } if (AdvancedSkinRegistry.TryGet(renderer.sharedMesh, out var _)) { throw new ArgumentException("Configure the native renderer before applying an advanced skin"); } Transform[] array = renderer.bones; if (array == null || array.Length != fallback.bindposes.Length || Array.Exists(array, (Transform b) => !Object.op_Implicit((Object)(object)b))) { throw new ArgumentException("Invalid native skeleton snapshot"); } if (fallbackMaterials == null && (Object)(object)renderer.sharedMesh != (Object)(object)fallback) { throw new ArgumentException("Capture native materials before applying a custom skin"); } Material[] array2 = fallbackMaterials ?? ((Renderer)renderer).sharedMaterials; if (array2.Length == 0 || Array.Exists(array2, (Material m) => !Object.op_Implicit((Object)(object)m))) { throw new ArgumentException("Missing native fallback materials"); } ConfigureSnapshot(renderer, fallback, source, array, renderer.rootBone, ((Renderer)renderer).localBounds, renderer.updateWhenOffscreen, array2); } internal void ConfigureSnapshot(SkinnedMeshRenderer renderer, Mesh fallback, Animator source, Transform[] nativeBones, Transform root, Bounds bounds, bool offscreen, Material[] materials) { //IL_00e3: 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) if (active != null) { throw new InvalidOperationException("Cannot replace an active native snapshot"); } if (!Object.op_Implicit((Object)(object)renderer) || !Object.op_Implicit((Object)(object)fallback) || !Object.op_Implicit((Object)(object)source) || nativeBones == null || nativeBones.Length != fallback.bindposes.Length || Array.Exists(nativeBones, (Transform b) => !Object.op_Implicit((Object)(object)b)) || materials == null || materials.Length == 0 || Array.Exists(materials, (Material m) => !Object.op_Implicit((Object)(object)m))) { throw new ArgumentException("Incomplete native rig snapshot"); } target = renderer; stockMesh = fallback; animator = source; stockMaterials = (Material[])materials.Clone(); stockBones = (Transform[])nativeBones.Clone(); stockRoot = root; stockBounds = bounds; stockOffscreen = offscreen; configured = true; failedMesh = null; } private static Vector3 V(V3 v) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(v.X, v.Y, v.Z); } private static Quaternion Q(Q4 q) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) return new Quaternion(q.X, q.Y, q.Z, q.W); } private void Activate(AdvancedSkinDefinition definition) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0180: 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_0190: Unknown result type (might be due to invalid IL or missing references) if (!configured || !Object.op_Implicit((Object)(object)target) || !Object.op_Implicit((Object)(object)stockMesh) || !Object.op_Implicit((Object)(object)animator)) { throw new InvalidOperationException("Native renderer must be configured before activating a custom rig"); } active = definition; container = new GameObject("EclipseVisualRig_" + definition.Motion.SkinId); container.transform.SetParent(((Component)target).transform, false); MotionBank motion = definition.Motion; bones = (Transform[])(object)new Transform[motion.Bones.Length]; pose = new Pose[bones.Length]; mixer = new PoseMixer(motion, definition.FallbackClip); for (int i = 0; i < bones.Length; i++) { Bone bone = motion.Bones[i]; bones[i] = new GameObject(bone.Name).transform; bones[i].SetParent((bone.Parent < 0) ? container.transform : bones[bone.Parent], false); pose[i] = bone.Rest; } mixer.Evaluate(new MotionLayer[0], pose); ApplyPose(); target.bones = bones; target.rootBone = bones[0]; ((Renderer)target).localBounds = new Bounds(V(motion.BoundsCenter), V(motion.BoundsSize)); target.updateWhenOffscreen = true; stateClocks.Clear(); unknown.Clear(); cues.Clear(); ResetAudioAttempts(); EnsureAudio(); } private void ResetAudioAttempts() { audioAttempted = false; audioAttempts = 0; nextAudioAttempt = 0f; } private void DeferAudio() { StopAudio(); audioAttempted = audioAttempts >= 6; nextAudioAttempt = Time.unscaledTime + Mathf.Min(16f, Mathf.Pow(2f, (float)(audioAttempts - 1))); } private void EnsureAudio() { if (!((Behaviour)this).isActiveAndEnabled || audioAttempted || active == null || audio != null || Time.unscaledTime < nextAudioAttempt) { return; } audioAttempted = true; audioAttempts++; try { if (active.CreateAudio != null) { audio = active.CreateAudio(FindAnchor); if (audio == null) { DeferAudio(); } } } catch (SkinAudioContextUnavailableException) { DeferAudio(); } catch (Exception ex2) { Debug.LogError((object)("ECLIPSE_CUSTOM_AUDIO_UNAVAILABLE " + ex2)); StopAudio(); } } private void StopAudio() { SkinAudioRouter skinAudioRouter = audio; audio = null; if (skinAudioRouter != null) { SkinAudioCleanupPump.Release(skinAudioRouter); } cues.Clear(); } private void EmitCue(string id) { if (audio != null) { try { audio.TryCue(id, Time.unscaledTime); } catch (Exception ex) { Debug.LogError((object)("ECLIPSE_CUSTOM_AUDIO_FAILED " + ex)); StopAudio(); } } } public NativeSoundDecision TryReplaceNativeSound(string exactEvent) { if (!((Behaviour)this).isActiveAndEnabled || audio == null) { return NativeSoundDecision.Forward; } try { return audio.TryReplaceNative(exactEvent, Time.unscaledTime); } catch (Exception ex) { Debug.LogError((object)("ECLIPSE_CUSTOM_AUDIO_FAILED " + ex)); StopAudio(); return NativeSoundDecision.Forward; } } private Transform FindAnchor(string name) { if (active == null) { return null; } for (int i = 0; i < bones.Length; i++) { if (active.Motion.Bones[i].Name == name) { return bones[i]; } } return null; } private void ApplyPose() { //IL_001d: 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_0063: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < bones.Length; i++) { bones[i].localPosition = V(pose[i].Position); bones[i].localRotation = Q(pose[i].Rotation); bones[i].localScale = V(pose[i].Scale); } } private int SourceRank(string group, string layer) { if (!active.NativeLayerPreference.TryGetValue(group, out var value)) { return 0; } int num = Array.IndexOf(value, layer); if (num >= 0) { return value.Length - num; } return 0; } private void Collect(List<AnimatorClipInfo> infos, int layer, AnimatorStateInfo state, MotionOccurrence occurrence, float transitionWeight, List<NativeMotionCandidate> candidates) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) float num = infos.Sum((AnimatorClipInfo i) => Math.Max(0f, ((AnimatorClipInfo)(ref i)).weight)); if ((double)num <= 1E-06) { return; } string layerName = animator.GetLayerName(layer); float num2 = ((layer == 0) ? 1f : animator.GetLayerWeight(layer)); if (num2 <= 0f) { return; } foreach (AnimatorClipInfo info2 in infos) { AnimatorClipInfo info = info2; if (!Object.op_Implicit((Object)(object)((AnimatorClipInfo)(ref info)).clip) || ((AnimatorClipInfo)(ref info)).weight <= 0f) { continue; } MotionBinding binding = active.Motion.Bindings.FirstOrDefault((MotionBinding b) => b.NativeClip == ((Object)((AnimatorClipInfo)(ref info)).clip).name && (b.Layer == "*" || b.Layer == layerName)); if (binding == null) { string text = layerName + "/" + ((Object)((AnimatorClipInfo)(ref info)).clip).name; if (unknown.Add(text)) { Debug.LogWarning((object)("ECLIPSE_UNMAPPED_MOTION " + active.Motion.SkinId + " " + text)); } continue; } MotionClip motionClip = active.Motion.Find(binding.Clip); double time = ((AnimatorStateInfo)(ref state)).normalizedTime; if (binding.Clock == MotionClock.Elapsed) { time = occurrence.Elapsed(Time.time, motionClip.Duration); } else if (binding.Clock == MotionClock.Parameter) { time = animator.GetFloat(binding.Parameter); } WeightedMotion weightedMotion = new WeightedMotion(); weightedMotion.Clip = motionClip; weightedMotion.Time = time; weightedMotion.Weight = ((AnimatorClipInfo)(ref info)).weight / num * transitionWeight * num2; weightedMotion.Token = layer + ":" + occurrence.Serial + ":" + motionClip.Id; WeightedMotion weightedMotion2 = weightedMotion; NativeMotionCandidate nativeMotionCandidate = candidates.FirstOrDefault((NativeMotionCandidate c) => c.SourceLayer == layer && c.Motion.Group == binding.Group); if (nativeMotionCandidate == null) { nativeMotionCandidate = new NativeMotionCandidate { SourceLayer = layer, SourceRank = SourceRank(binding.Group, layerName), Motion = new MotionLayer { Group = binding.Group, Priority = binding.Priority, Blend = binding.Blend, Samples = new WeightedMotion[0] } }; candidates.Add(nativeMotionCandidate); } nativeMotionCandidate.Motion.Samples = nativeMotionCandidate.Motion.Samples.Concat(new WeightedMotion[1] { weightedMotion2 }).ToArray(); } } private void EnsureMotionSources() { if (((Behaviour)this).isActiveAndEnabled && active != null && !motionSourcesInitialized) { if (active.CreateMotionSource != null) { motionSource = active.CreateMotionSource(animator, active.Motion); } if (active.CreateMotionTiming != null) { motionTiming = active.CreateMotionTiming(target, active.Motion); } motionSourcesInitialized = true; } } private void UpdateEffects() { if (!effectsAttempted) { effectsAttempted = true; if (active.CreateEffects != null) { effects = active.CreateEffects(target); } } effects?.Update(); } private void StopEffects() { ISkinEffectDriver skinEffectDriver = effects; effects = null; effectsAttempted = false; if (skinEffectDriver != null) { try { skinEffectDriver.Dispose(); } catch (Exception ex) { Debug.LogError((object)("ECLIPSE_CUSTOM_EFFECTS_CLEANUP_FAILED " + ex)); } } } private void UpdateMotion() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_006c: 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_0060: 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_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) List<NativeMotionCandidate> list = new List<NativeMotionCandidate>(); for (int i = 0; i < animator.layerCount; i++) { bool flag = animator.IsInTransition(i); float num; if (!flag) { num = 0f; } else { AnimatorTransitionInfo animatorTransitionInfo = animator.GetAnimatorTransitionInfo(i); num = Mathf.Clamp01(((AnimatorTransitionInfo)(ref animatorTransitionInfo)).normalizedTime); } float num2 = num; AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimator