Decompiled source of ItemUnstuck v1.0.3
ItemUnstuck.dll
Decompiled 2 weeks 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.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("Tolga")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3+abf9373dff71aca88819e1d28251b7dab3a82367")] [assembly: AssemblyProduct("ItemUnstuck")] [assembly: AssemblyTitle("ItemUnstuck")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ItemUnstuck { internal static class ConfigEntries { internal static ConfigEntry<bool> Enable { get; set; } internal static ConfigEntry<bool> HudNotifications { get; set; } internal static ConfigEntry<bool> ShowInitialValueOnGrab { get; set; } internal static ConfigEntry<bool> EnableRelocation { get; set; } internal static ConfigEntry<bool> InvulnerableUntilGrab { get; set; } internal static ConfigEntry<bool> RestoreValueToInitial { get; set; } internal static ConfigEntry<bool> VerboseLogging { get; set; } internal static bool MidGameHudNotifications => true; internal static float ScanDelaySeconds => 1f; internal static float OccupiedDistance => 0.2f; internal static float StuckMinPenetrationDistance => 0.12f; internal static float StuckWeakPenetrationDistance => 0.03f; internal static bool RequireStaticWorldEvidenceForRelocation => true; internal static bool UseFallbackPositions => true; internal static bool EnableContainerDetection => true; internal static ConfigEntry<bool> EnableMidGameDetection { get; set; } internal static float MidGameScanDelaySeconds => 2.5f; internal static bool MotionProbeEnabled => true; internal static float MotionProbeDelaySeconds => 2f; internal static float MotionProbeWindowSeconds => 0.75f; internal static int MotionProbeSamples => 12; internal static float MotionMaxSpeedToFlag => 0.75f; internal static int MotionMinDirectionReversalsToFlag => 2; internal static float MotionReversalDotThreshold => -0.2f; internal static bool FallThroughRecoveryEnabled => true; internal static float FallThroughYThreshold => -50f; internal static float FallThroughWatchSeconds => 15f; internal static float FallThroughPollSeconds => 0.25f; internal static int FallThroughMaxRecoveriesPerItem => 2; internal static float DamageCheckSeconds => 3f; internal static int MaxRelocationsPerItem => 2; internal static float MinDamageToTrigger => 1f; } internal class FallThroughDetector : MonoBehaviour { private class FallState { public Vector3 StartPosition; public float StartTime; public float LastY; public bool IsFalling; } private Dictionary<int, FallState> trackedItems = new Dictionary<int, FallState>(); private float checkInterval = ConfigEntries.FallThroughPollSeconds; private float nextCheckTime; private ValuableObject[]? cachedValuables; private float cacheExpiry; private const float CacheRefreshInterval = 2f; private float minFallSpeed = 2f; private float minFallDuration = 3f; private float maxFallDuration = 10f; public void Reset() { trackedItems.Clear(); cachedValuables = null; cacheExpiry = 0f; } private ValuableObject[] GetValuables() { if (cachedValuables == null || Time.time > cacheExpiry) { cachedValuables = Object.FindObjectsOfType<ValuableObject>(); cacheExpiry = Time.time + 2f; } return cachedValuables; } private void Update() { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) if (!ConfigEntries.Enable.Value || !ConfigEntries.FallThroughRecoveryEnabled || Time.time < nextCheckTime) { return; } nextCheckTime = Time.time + checkInterval; ValuableObject[] valuables = GetValuables(); HashSet<int> hashSet = new HashSet<int>(); ValuableObject[] array = valuables; foreach (ValuableObject val in array) { if ((Object)(object)val == (Object)null) { continue; } PhysGrabObject physGrabObject = val.physGrabObject; if ((Object)(object)physGrabObject == (Object)null || ((Object)(object)physGrabObject.impactDetector != (Object)null && physGrabObject.impactDetector.inCart)) { continue; } int id = ValueDiagnostics.GetId(val); if (id == 0) { continue; } hashSet.Add(id); float y = ((Component)val).transform.position.y; if (!trackedItems.TryGetValue(id, out FallState value)) { value = new FallState { StartPosition = ((Component)val).transform.position, StartTime = Time.time, LastY = y, IsFalling = false }; trackedItems[id] = value; continue; } float num = value.LastY - y; _ = Time.time; _ = value.StartTime; float num2 = num / checkInterval; if (num2 >= minFallSpeed) { if (!value.IsFalling) { value.IsFalling = true; value.StartTime = Time.time; value.StartPosition = ((Component)val).transform.position; } else { float num3 = Time.time - value.StartTime; if (num3 >= minFallDuration) { ItemUnstuck.Logger.LogWarning((object)("[ItemUnstuck] Fall-through detected for '" + ((Object)val).name + "' " + $"(falling for {num3:F1}s at {num2:F1}m/s, " + $"Y: {value.StartPosition.y:F1} -> {y:F1})")); StuckValuableRelocator.TryRecoverFallThrough(val); trackedItems.Remove(id); continue; } if (num3 > maxFallDuration) { trackedItems.Remove(id); continue; } } } else if (value.IsFalling) { value.IsFalling = false; value.StartTime = Time.time; value.StartPosition = ((Component)val).transform.position; } value.LastY = y; } List<int> list = new List<int>(); foreach (int key in trackedItems.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (int item in list) { trackedItems.Remove(item); } } } internal static class FallThroughRecovery { [CompilerGenerated] private sealed class <WatchCoroutine>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ValuableObject valuable; public int id; public string label; private float <thresholdY>5__2; private float <poll>5__3; private int <maxRecoveries>5__4; private float <end>5__5; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WatchCoroutine>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>1__state = -2; } private bool MoveNext() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) bool result; try { switch (<>1__state) { default: result = false; goto end_IL_0000; case 0: { <>1__state = -1; <>1__state = -3; <thresholdY>5__2 = ConfigEntries.FallThroughYThreshold; float num = Mathf.Max(0.5f, ConfigEntries.FallThroughWatchSeconds); <poll>5__3 = Mathf.Clamp(ConfigEntries.FallThroughPollSeconds, 0.05f, 2f); <maxRecoveries>5__4 = Mathf.Clamp(ConfigEntries.FallThroughMaxRecoveriesPerItem, 0, 10); <end>5__5 = Time.time + num; goto IL_029f; } case 1: { <>1__state = -3; if (!ConfigEntries.Enable.Value || !ConfigEntries.FallThroughRecoveryEnabled) { result = false; break; } if ((Object)(object)valuable == (Object)null) { result = false; break; } PhysGrabObject physGrabObject = valuable.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { result = false; break; } if ((Object)(object)physGrabObject.impactDetector != (Object)null && physGrabObject.impactDetector.inCart) { result = false; break; } float y = ((Component)valuable).transform.position.y; if (!(y >= <thresholdY>5__2)) { RecoveryCounts.TryGetValue(id, out var value); if (value >= <maxRecoveries>5__4) { ItemUnstuck.Logger.LogWarning((object)$"[ItemUnstuck] Fall-through detected for '{((Object)valuable).name}' (y={y:0.0}) but max recoveries reached ({value}/{<maxRecoveries>5__4})."); HudNotifier.ShowFallThrough(label, valuable, y, recovered: false, value, <maxRecoveries>5__4); result = false; break; } RecoveryCounts[id] = value + 1; if (!StuckValuableRelocator.TryRecoverFallThrough(valuable)) { ItemUnstuck.Logger.LogWarning((object)$"[ItemUnstuck] Fall-through detected for '{((Object)valuable).name}' (y={y:0.0}) after {label} relocation, but no safe slot found to recover."); HudNotifier.ShowFallThrough(label, valuable, y, recovered: false, value + 1, <maxRecoveries>5__4); result = false; break; } InvulnerabilityController.ApplyUntilGrab(physGrabObject); ItemUnstuck.Logger.LogWarning((object)$"[ItemUnstuck] Recovered fall-through for '{((Object)valuable).name}' (y={y:0.0}) after {label} relocation; recovery #{value + 1}."); HudNotifier.ShowFallThrough(label, valuable, y, recovered: true, value + 1, <maxRecoveries>5__4); } goto IL_029f; } IL_029f: if (Time.time < <end>5__5) { <>2__current = (object)new WaitForSeconds(<poll>5__3); <>1__state = 1; result = true; } else { <>m__Finally1(); result = false; } goto end_IL_0000; } <>m__Finally1(); end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; Watching.Remove(id); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly HashSet<int> Watching = new HashSet<int>(); private static readonly Dictionary<int, int> RecoveryCounts = new Dictionary<int, int>(); public static void Reset() { Watching.Clear(); RecoveryCounts.Clear(); } public static void WatchRelocated(ValuableObject valuable, string label) { if (ConfigEntries.Enable.Value && ConfigEntries.FallThroughRecoveryEnabled && !((Object)(object)ItemUnstuck.Instance == (Object)null) && !((Object)(object)valuable == (Object)null)) { int id = ValueDiagnostics.GetId(valuable); if (id != 0 && Watching.Add(id)) { ((MonoBehaviour)ItemUnstuck.Instance).StartCoroutine(WatchCoroutine(valuable, id, label)); } } } [IteratorStateMachine(typeof(<WatchCoroutine>d__4))] private static IEnumerator WatchCoroutine(ValuableObject valuable, int id, string label) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WatchCoroutine>d__4(0) { valuable = valuable, id = id, label = label }; } } internal static class FreeSpaceProbe { public static bool HasFreeSpace(ValuableObject valuable, float upwardDot) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: 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_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)valuable == (Object)null) { return false; } PhysGrabObject physGrabObject = valuable.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { return false; } Bounds bounds = default(Bounds); ((Bounds)(ref bounds))..ctor(physGrabObject.midPoint, Vector3.one * 0.1f); Collider[] componentsInChildren = ((Component)valuable).GetComponentsInChildren<Collider>(); if (componentsInChildren != null && componentsInChildren.Length != 0) { bounds = componentsInChildren[0].bounds; for (int i = 1; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null && !componentsInChildren[i].isTrigger) { ((Bounds)(ref bounds)).Encapsulate(componentsInChildren[i].bounds); } } } Vector3 center = ((Bounds)(ref bounds)).center; Camera main = Camera.main; Vector3[] array2; if ((Object)(object)main != (Object)null) { Vector3[] array = (Vector3[])(object)new Vector3[3] { center, center + Vector3.up * ((Bounds)(ref bounds)).extents.y * 0.5f, center + Vector3.up * ((Bounds)(ref bounds)).extents.y }; int num = ~LayerMask.GetMask(new string[4] { "Player", "Triggers", "PlayerOnlyCollision", "UI" }); array2 = array; foreach (Vector3 val in array2) { Vector3 val2 = ((Component)main).transform.position - val; float magnitude = ((Vector3)(ref val2)).magnitude; if (!Physics.Raycast(val, ((Vector3)(ref val2)).normalized, magnitude - 0.1f, num, (QueryTriggerInteraction)1)) { return true; } } } if (upwardDot < -0.3f) { float num2 = ((Bounds)(ref bounds)).extents.y + 0.15f; if (Physics.Raycast(center, Vector3.down, num2, LayerMask.GetMask(new string[2] { "Default", "Environment" }), (QueryTriggerInteraction)1)) { return true; } } float num3 = 0.5f; Vector3[] array3 = (Vector3[])(object)new Vector3[5] { Vector3.forward, Vector3.back, Vector3.left, Vector3.right, Vector3.up }; int num4 = 0; int mask = LayerMask.GetMask(new string[2] { "Default", "Environment" }); array2 = array3; foreach (Vector3 val3 in array2) { if (!Physics.Raycast(center, val3, num3, mask, (QueryTriggerInteraction)1)) { num4++; } } if (num4 >= 1) { return true; } return false; } } internal static class HudNotifier { private static string CleanName(string? name) { if (string.IsNullOrWhiteSpace(name)) { return "Valuable"; } string text = name; if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Replace("(Clone)", "", StringComparison.Ordinal); } return text; } public static void ShowRelocated(IReadOnlyList<RelocatedValuableInfo> relocated, float totalLostFromBaseline) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) if (!ConfigEntries.HudNotifications.Value || relocated.Count == 0) { return; } int num = 5; List<string> values = (from x in relocated.Take(num) select $"{x.Name} (${(int)x.ValueCurrentAtRelocation})").ToList(); string text = ((relocated.Count > num) ? $" +{relocated.Count - num}" : ""); string text2 = $"ItemUnstuck: relocated {relocated.Count} stuck valuable(s)\n" + $"Lost vs baseline: ${(int)totalLostFromBaseline}\n" + string.Join(", ", values) + text; try { SemiFunc.UIBigMessage(text2, "{check}", 20f, Color.white, Color.white); } catch (Exception ex) { if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogDebug((object)("UIBigMessage failed: " + ex.Message)); } } } public static void ShowFallThrough(string label, ValuableObject valuable, float y, bool recovered, int recoveryNumber, int maxRecoveries) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //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 (!ConfigEntries.HudNotifications.Value || (Object)(object)valuable == (Object)null) { return; } string arg = CleanName(((Object)valuable).name); string text = (recovered ? "recovered" : "FAILED (no safe slot)"); string text2 = "ItemUnstuck: Item fell from map - " + text + "\n" + $"{arg} (${(int)valuable.dollarValueCurrent}) fell to Y={y:0.0}\n" + $"Recovery attempt {recoveryNumber}/{maxRecoveries}"; if (!recovered) { _ = Color.red; } else { _ = Color.green; } try { SemiFunc.UIFocusText(text2, Color.white, Color.white, 3f); } catch (Exception ex) { if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogDebug((object)("UIFocusText failed: " + ex.Message)); } } } public static void ShowMidGameRelocation(string itemName) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (!ConfigEntries.HudNotifications.Value || !ConfigEntries.MidGameHudNotifications) { return; } string text = CleanName(itemName); string text2 = "ItemUnstuck: Relocated stuck spawn\n" + text; try { SemiFunc.UIFocusText(text2, Color.white, Color.green, 3f); } catch (Exception ex) { if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogDebug((object)("UIFocusText failed: " + ex.Message)); } } } public static void Show(string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { SemiFunc.UIFocusText(message, Color.white, Color.cyan, 4f); } catch (Exception ex) { if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogDebug((object)("UIFocusText failed: " + ex.Message)); } } } } internal static class InvulnerabilityController { private static readonly FieldInfo? IndestructibleSpawnTimerField = typeof(PhysGrabObjectImpactDetector).GetField("indestructibleSpawnTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); public static void ApplyUntilGrab(PhysGrabObject physGrabObject) { if (!ConfigEntries.Enable.Value || !ConfigEntries.InvulnerableUntilGrab.Value || (Object)(object)physGrabObject == (Object)null) { return; } PhysGrabObjectImpactDetector component = ((Component)physGrabObject).GetComponent<PhysGrabObjectImpactDetector>(); if (!((Object)(object)component == (Object)null)) { if (IndestructibleSpawnTimerField == null) { ItemUnstuck.Logger.LogError((object)"Could not find PhysGrabObjectImpactDetector.indestructibleSpawnTimer; invulnerability not applied."); return; } float num = 99999f; IndestructibleSpawnTimerField.SetValue(component, num); physGrabObject.OverrideIndestructible(0.5f); } } public static void Clear(PhysGrabObject physGrabObject) { if (ConfigEntries.Enable.Value && ConfigEntries.InvulnerableUntilGrab.Value && !((Object)(object)physGrabObject == (Object)null)) { PhysGrabObjectImpactDetector component = ((Component)physGrabObject).GetComponent<PhysGrabObjectImpactDetector>(); if ((Object)(object)component != (Object)null && IndestructibleSpawnTimerField != null) { IndestructibleSpawnTimerField.SetValue(component, 0f); } physGrabObject.ResetIndestructible(); } } } [BepInPlugin("Tolga.ItemUnstuck", "ItemUnstuck", "1.0.3")] public class ItemUnstuck : BaseUnityPlugin { [StructLayout(LayoutKind.Auto)] [CompilerGenerated] private struct <>c__DisplayClass20_0 { public List<ValuableObject> cachedValuables; } [CompilerGenerated] private sealed class <ProcessMidGameBatch>d__26 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ItemUnstuck <>4__this; private int <currentGeneration>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ProcessMidGameBatch>d__26(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown int num = <>1__state; ItemUnstuck itemUnstuck = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; float num2 = Mathf.Max(0.5f, ConfigEntries.MidGameScanDelaySeconds); <>2__current = (object)new WaitForSeconds(num2); <>1__state = 1; return true; } case 1: <>1__state = -1; <currentGeneration>5__2 = itemUnstuck.scanGeneration; break; case 2: <>1__state = -1; break; } while (itemUnstuck.midGameSpawnQueue.Count > 0) { if (itemUnstuck.scanGeneration != <currentGeneration>5__2) { itemUnstuck.midGameSpawnQueue.Clear(); break; } ValuableObject[] allValuables = Object.FindObjectsOfType<ValuableObject>() ?? Array.Empty<ValuableObject>(); int num3 = 0; while (itemUnstuck.midGameSpawnQueue.Count > 0 && num3 < 5) { ValuableObject val = itemUnstuck.midGameSpawnQueue.Dequeue(); if (!((Object)(object)val == (Object)null)) { PhysGrabObject physGrabObject = val.physGrabObject; if (!((Object)(object)physGrabObject == (Object)null) && (!((Object)(object)physGrabObject.impactDetector != (Object)null) || !physGrabObject.impactDetector.inCart) && !StuckValuableRelocator.ShouldSkipEmbedmentCheck(val, out string _)) { StuckValuableRelocator.TryRelocateSingleMidGameSpawn(val, allValuables); num3++; } } } if (ConfigEntries.VerboseLogging.Value && num3 > 0) { Logger.LogInfo((object)$"[ItemUnstuck][Batch] Processed {num3} mid-game spawns, {itemUnstuck.midGameSpawnQueue.Count} remaining"); } if (itemUnstuck.midGameSpawnQueue.Count > 0) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 2; return true; } } itemUnstuck.midGameBatchCoroutine = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <ProcessSingleMidGameSpawn>d__25 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ValuableObject valuable; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ProcessSingleMidGameSpawn>d__25(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForFixedUpdate(); <>1__state = 1; return true; case 1: { <>1__state = -1; if ((Object)(object)valuable == (Object)null) { return false; } ValuableObject[] allValuables = Object.FindObjectsOfType<ValuableObject>() ?? Array.Empty<ValuableObject>(); StuckValuableRelocator.TryRelocateSingleMidGameSpawn(valuable, allValuables); return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <ScanAfterVolumesSetup>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int generation; public ItemUnstuck <>4__this; private <>c__DisplayClass20_0 <>8__1; private ValuableObject[] <valuablesAtBaseline>5__2; private HashSet<int> <affectedIds>5__3; private HashSet<int> <relocatedIds>5__4; private List<RelocatedValuableInfo> <relocatedInfos>5__5; private Dictionary<int, MotionStats> <motionStats>5__6; private float <damageWait>5__7; private float <minDamage>5__8; private int <maxRelocationsPerItem>5__9; private float <probeDelay>5__10; private float <window>5__11; private int <samples>5__12; private float <dt>5__13; private float <reversalDotThreshold>5__14; private List<ValuableObject> <tracked>5__15; private List<int> <ids>5__16; private List<Vector3> <prevPos>5__17; private List<Vector3> <prevDelta>5__18; private List<float> <sumDist>5__19; private List<float> <maxSpeed>5__20; private List<int> <reversals>5__21; private int <s>5__22; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ScanAfterVolumesSetup>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = default(<>c__DisplayClass20_0); <valuablesAtBaseline>5__2 = null; <affectedIds>5__3 = null; <relocatedIds>5__4 = null; <relocatedInfos>5__5 = null; <motionStats>5__6 = null; <tracked>5__15 = null; <ids>5__16 = null; <prevPos>5__17 = null; <prevDelta>5__18 = null; <sumDist>5__19 = null; <maxSpeed>5__20 = null; <reversals>5__21 = null; <>1__state = -2; } private bool MoveNext() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0460: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_0471: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Expected O, but got Unknown //IL_0552: Unknown result type (might be due to invalid IL or missing references) //IL_0561: Unknown result type (might be due to invalid IL or missing references) //IL_04ed: Unknown result type (might be due to invalid IL or missing references) //IL_04f2: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Expected O, but got Unknown //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_0703: Unknown result type (might be due to invalid IL or missing references) //IL_070d: Expected O, but got Unknown int num = <>1__state; ItemUnstuck itemUnstuck = <>4__this; int count; ValuableObject[] array; List<ValuableObject> valuablesWithLoss; ValuableObject[] array2; switch (num) { default: return false; case 0: { <>1__state = -1; if (!ConfigEntries.Enable.Value) { return false; } float num3 = Mathf.Max(0f, ConfigEntries.ScanDelaySeconds); if (num3 > 0f) { <>2__current = (object)new WaitForSeconds(num3); <>1__state = 1; return true; } goto IL_0073; } case 1: <>1__state = -1; goto IL_0073; case 2: <>1__state = -1; goto IL_025c; case 3: { <>1__state = -1; if (generation != itemUnstuck.scanGeneration) { return false; } for (int i = 0; i < <tracked>5__15.Count; i++) { ValuableObject val = <tracked>5__15[i]; if ((Object)(object)val == (Object)null) { continue; } Vector3 position = ((Component)val).transform.position; Vector3 value = position - <prevPos>5__17[i]; float magnitude = ((Vector3)(ref value)).magnitude; <sumDist>5__19[i] += magnitude; float num2 = magnitude / Mathf.Max(0.0001f, <dt>5__13); if (num2 > <maxSpeed>5__20[i]) { <maxSpeed>5__20[i] = num2; } if (<s>5__22 >= 2) { Vector3 val2 = <prevDelta>5__18[i]; if (((Vector3)(ref val2)).sqrMagnitude > 1E-06f && ((Vector3)(ref value)).sqrMagnitude > 1E-06f && Vector3.Dot(((Vector3)(ref val2)).normalized, ((Vector3)(ref value)).normalized) <= <reversalDotThreshold>5__14) { <reversals>5__21[i]++; } } <prevDelta>5__18[i] = value; <prevPos>5__17[i] = position; } <s>5__22++; goto IL_0592; } case 4: { <>1__state = -1; goto IL_071d; } IL_0073: if (generation != itemUnstuck.scanGeneration) { return false; } if (SpawnSlotRegistry.Slots.Count == 0) { if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)"[ItemUnstuck] Scan skipped: no spawn slots captured yet."); } return false; } count = SpawnSlotRegistry.Slots.Count; Logger.LogInfo((object)$"[ItemUnstuck] Level loaded: {count} free (spawn) slots."); try { SemiFunc.UIBigMessage($"ItemUnstuck: {count} free slots", "{check}", 4f, Color.white, Color.white); } catch { } <>8__1.cachedValuables = new List<ValuableObject>(Object.FindObjectsOfType<ValuableObject>() ?? Array.Empty<ValuableObject>()); if (<>8__1.cachedValuables.Count == 0) { if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)"[ItemUnstuck] Scan skipped: no valuables found."); } return false; } <valuablesAtBaseline>5__2 = <ScanAfterVolumesSetup>g__GetLiveSnapshot|20_0(ref <>8__1); if (<valuablesAtBaseline>5__2.Length == 0) { if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)"[ItemUnstuck] Scan skipped: all cached valuables were destroyed before baseline capture."); } return false; } <affectedIds>5__3 = new HashSet<int>(); <relocatedIds>5__4 = new HashSet<int>(); <relocatedInfos>5__5 = new List<RelocatedValuableInfo>(); ValueDiagnostics.CaptureBaselineIfNeeded(<valuablesAtBaseline>5__2); if (ConfigEntries.EnableRelocation.Value) { foreach (RelocatedValuableInfo item in StuckValuableRelocator.RelocateWorldEmbedded(<valuablesAtBaseline>5__2, null).Relocated) { <relocatedInfos>5__5.Add(item); } } <motionStats>5__6 = null; if (ConfigEntries.MotionProbeEnabled) { <probeDelay>5__10 = Mathf.Max(0f, ConfigEntries.MotionProbeDelaySeconds); if (<probeDelay>5__10 > 0f) { <>2__current = (object)new WaitForSeconds(<probeDelay>5__10); <>1__state = 2; return true; } goto IL_025c; } goto IL_06a6; IL_0592: if (<s>5__22 < <samples>5__12) { <>2__current = (object)new WaitForSeconds(<dt>5__13); <>1__state = 3; return true; } for (int j = 0; j < <tracked>5__15.Count; j++) { float averageSpeed = <sumDist>5__19[j] / <window>5__11; <motionStats>5__6[<ids>5__16[j]] = new MotionStats(averageSpeed, <maxSpeed>5__20[j], <reversals>5__21[j], <reversalDotThreshold>5__14); } if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)$"[ItemUnstuck][Debug] Motion probe captured stats for {<motionStats>5__6.Count} valuables (delay={<probeDelay>5__10:0.00}s window={<window>5__11:0.00}s samples={<samples>5__12})."); } <tracked>5__15 = null; <ids>5__16 = null; <prevPos>5__17 = null; <prevDelta>5__18 = null; <sumDist>5__19 = null; <maxSpeed>5__20 = null; <reversals>5__21 = null; goto IL_06a6; IL_071d: if (generation != itemUnstuck.scanGeneration) { return false; } array = <ScanAfterVolumesSetup>g__GetLiveSnapshot|20_0(ref <>8__1); valuablesWithLoss = ValueDiagnostics.GetValuablesWithLoss(array, <minDamage>5__8); if (valuablesWithLoss.Count == 0) { if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)$"[ItemUnstuck][Debug] Damage check pass {<samples>5__12}: no damaged valuables detected."); } if (<samples>5__12 == 1) { break; } } else { List<ValuableObject> list; if (<samples>5__12 == 1) { list = valuablesWithLoss; } else { list = new List<ValuableObject>(); foreach (ValuableObject item2 in valuablesWithLoss) { if (!((Object)(object)item2 == (Object)null) && <relocatedIds>5__4.Contains(ValueDiagnostics.GetId(item2))) { list.Add(item2); } } } if (list.Count != 0) { foreach (ValuableObject item3 in list) { if (!((Object)(object)item3 == (Object)null)) { <affectedIds>5__3.Add(ValueDiagnostics.GetId(item3)); } } foreach (RelocatedValuableInfo item4 in StuckValuableRelocator.RelocateCandidates(list, array, <motionStats>5__6).Relocated) { <relocatedInfos>5__5.Add(item4); } foreach (ValuableObject item5 in list) { if (!((Object)(object)item5 == (Object)null)) { <relocatedIds>5__4.Add(ValueDiagnostics.GetId(item5)); } } } } <samples>5__12++; goto IL_08fa; IL_08fa: if (<samples>5__12 > <maxRelocationsPerItem>5__9) { break; } if (<damageWait>5__7 > 0f) { <>2__current = (object)new WaitForSeconds(<damageWait>5__7); <>1__state = 4; return true; } goto IL_071d; IL_06a6: <damageWait>5__7 = Mathf.Max(0f, ConfigEntries.DamageCheckSeconds); <minDamage>5__8 = Mathf.Max(0f, ConfigEntries.MinDamageToTrigger); <maxRelocationsPerItem>5__9 = Mathf.Clamp(ConfigEntries.MaxRelocationsPerItem, 0, 10); <samples>5__12 = 1; goto IL_08fa; IL_025c: if (generation != itemUnstuck.scanGeneration) { return false; } <motionStats>5__6 = new Dictionary<int, MotionStats>(); <window>5__11 = Mathf.Max(0.05f, ConfigEntries.MotionProbeWindowSeconds); <samples>5__12 = Mathf.Clamp(ConfigEntries.MotionProbeSamples, 2, 60); <dt>5__13 = <window>5__11 / (float)(<samples>5__12 - 1); <reversalDotThreshold>5__14 = Mathf.Clamp(ConfigEntries.MotionReversalDotThreshold, -0.99f, 0.99f); <tracked>5__15 = new List<ValuableObject>(<valuablesAtBaseline>5__2.Length); <ids>5__16 = new List<int>(<valuablesAtBaseline>5__2.Length); <prevPos>5__17 = new List<Vector3>(<valuablesAtBaseline>5__2.Length); <prevDelta>5__18 = new List<Vector3>(<valuablesAtBaseline>5__2.Length); <sumDist>5__19 = new List<float>(<valuablesAtBaseline>5__2.Length); <maxSpeed>5__20 = new List<float>(<valuablesAtBaseline>5__2.Length); <reversals>5__21 = new List<int>(<valuablesAtBaseline>5__2.Length); array2 = <valuablesAtBaseline>5__2; foreach (ValuableObject val3 in array2) { if (!((Object)(object)val3 == (Object)null)) { <tracked>5__15.Add(val3); <ids>5__16.Add(ValueDiagnostics.GetId(val3)); <prevPos>5__17.Add(((Component)val3).transform.position); <prevDelta>5__18.Add(Vector3.zero); <sumDist>5__19.Add(0f); <maxSpeed>5__20.Add(0f); <reversals>5__21.Add(0); } } <s>5__22 = 1; goto IL_0592; } if (ConfigEntries.RestoreValueToInitial.Value && <affectedIds>5__3.Count > 0) { array2 = <ScanAfterVolumesSetup>g__GetLiveSnapshot|20_0(ref <>8__1); foreach (ValuableObject val4 in array2) { if (!((Object)(object)val4 == (Object)null) && <affectedIds>5__3.Contains(ValueDiagnostics.GetId(val4))) { ValueDiagnostics.RestoreValueToBaseline(val4); } } } ValuableObject[] valuables = <ScanAfterVolumesSetup>g__GetLiveSnapshot|20_0(ref <>8__1); (int, float, float, float, float) tuple = ValueDiagnostics.ComputeTotalsWithBaseline(valuables); if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)$"[ItemUnstuck] Post-flow totals: current=${(int)tuple.Item2}, original=${(int)tuple.Item3}, baselineCurrent=${(int)tuple.Item4}, lost=${(int)tuple.Item5}, count={tuple.Item1}"); } ValueDiagnostics.LogValueLosses(valuables); if (<relocatedInfos>5__5.Count > 0) { HudNotifier.ShowRelocated(<relocatedInfos>5__5, tuple.Item5); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private Coroutine? scanCoroutine; private int scanGeneration; private readonly Queue<ValuableObject> midGameSpawnQueue = new Queue<ValuableObject>(); private Coroutine? midGameBatchCoroutine; private const float BATCH_PROCESS_INTERVAL = 0.5f; private const int MAX_BATCH_SIZE = 5; internal static ItemUnstuck Instance { get; private set; } internal static ManualLogSource Logger => Instance._logger; private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger; internal Harmony? Harmony { get; set; } private void Awake() { Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; ConfigEntries.Enable = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable", true, "Enable/disable the ItemUnstuck mod."); ConfigEntries.EnableRelocation = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableRelocation", true, "If true, detects stuck valuables and relocates them to a free valuable spawn slot."); ConfigEntries.HudNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HudNotifications", true, "Show a HUD notification when stuck valuables are relocated."); ConfigEntries.ShowInitialValueOnGrab = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowInitialValueOnGrab", false, "Show a HUD message displaying the item's initial (undamaged) value when you grab it."); ConfigEntries.InvulnerableUntilGrab = ((BaseUnityPlugin)this).Config.Bind<bool>("Gameplay", "InvulnerableUntilGrab", true, "If true, make valuables invulnerable until any player grabs them."); ConfigEntries.RestoreValueToInitial = ((BaseUnityPlugin)this).Config.Bind<bool>("Gameplay", "RestoreValueToInitial", true, "If true, restore damaged/relocated valuables back to their initial value."); ConfigEntries.VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "Enable verbose logging for stuck detection and relocation decisions. Set to true for maximum logging during testing."); ConfigEntries.EnableMidGameDetection = ((BaseUnityPlugin)this).Config.Bind<bool>("Detection", "EnableMidGameDetection", true, "Enable detection and relocation of items that get stuck immediately after spawning mid-game (e.g. from vents/monsters)."); SpawnSlotRegistry.Clear(); Patch(); ((Component)this).gameObject.AddComponent<FallThroughDetector>(); if (ConfigEntries.VerboseLogging.Value) { RelocationAnalytics.Initialize(); Logger.LogInfo((object)("[Diagnostics] Log file: " + RelocationAnalytics.GetLogFilePath())); } Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded!"); } internal void NotifyValuableVolumesSetupComplete() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) scanGeneration++; StuckValuableRelocator.ClearCache(); Scene activeScene = SceneManager.GetActiveScene(); RelocationAnalytics.SetLevel(((Scene)(ref activeScene)).name); if (scanCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(scanCoroutine); scanCoroutine = null; } scanCoroutine = ((MonoBehaviour)this).StartCoroutine(ScanAfterVolumesSetup(scanGeneration)); } [IteratorStateMachine(typeof(<ScanAfterVolumesSetup>d__20))] private IEnumerator ScanAfterVolumesSetup(int generation) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ScanAfterVolumesSetup>d__20(0) { <>4__this = this, generation = generation }; } internal void Patch() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0025: Expected O, but got Unknown if (Harmony == null) { Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); Harmony val2 = val; Harmony = val; } Harmony.PatchAll(); } internal void Unpatch() { Harmony? harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void OnDestroy() { RelocationAnalytics.Shutdown(); } internal void ScheduleMidGameStuckCheck(ValuableObject valuable) { if (!ConfigEntries.Enable.Value || !ConfigEntries.EnableMidGameDetection.Value || !ConfigEntries.EnableRelocation.Value || (Object)(object)valuable == (Object)null) { return; } if (StuckValuableRelocator.WillEmbedSoon(valuable, out float _, out string reason)) { if (ConfigEntries.VerboseLogging.Value) { Logger.LogInfo((object)("[ItemUnstuck][Predictive] Item '" + ((Object)valuable).name + "' " + reason + " - immediate processing")); } ((MonoBehaviour)this).StartCoroutine(ProcessSingleMidGameSpawn(valuable)); } else { midGameSpawnQueue.Enqueue(valuable); if (midGameBatchCoroutine == null) { midGameBatchCoroutine = ((MonoBehaviour)this).StartCoroutine(ProcessMidGameBatch()); } } } [IteratorStateMachine(typeof(<ProcessSingleMidGameSpawn>d__25))] private IEnumerator ProcessSingleMidGameSpawn(ValuableObject valuable) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ProcessSingleMidGameSpawn>d__25(0) { valuable = valuable }; } [IteratorStateMachine(typeof(<ProcessMidGameBatch>d__26))] private IEnumerator ProcessMidGameBatch() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ProcessMidGameBatch>d__26(0) { <>4__this = this }; } [CompilerGenerated] internal static ValuableObject[] <ScanAfterVolumesSetup>g__GetLiveSnapshot|20_0(ref <>c__DisplayClass20_0 P_0) { List<ValuableObject> list = new List<ValuableObject>(P_0.cachedValuables.Count); for (int i = 0; i < P_0.cachedValuables.Count; i++) { ValuableObject val = P_0.cachedValuables[i]; if ((Object)(object)val != (Object)null) { list.Add(val); } } return list.ToArray(); } } public static class ItemUnstuckEvents { public static event Func<ValuableObject, Vector3, bool>? OnBeforeRelocate; public static event Action<ValuableObject, Vector3, Vector3>? OnItemRelocated; public static event Action<ValuableObject>? OnMidGameSpawnDetected; public static event Action<ValuableObject, string>? OnRelocationFailed; internal static bool RaiseBeforeRelocate(ValuableObject valuable, Vector3 targetPosition) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (ItemUnstuckEvents.OnBeforeRelocate == null) { return true; } Delegate[] invocationList = ItemUnstuckEvents.OnBeforeRelocate.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Func<ValuableObject, Vector3, bool> func = (Func<ValuableObject, Vector3, bool>)invocationList[i]; try { if (!func(valuable, targetPosition)) { return false; } } catch (Exception ex) { ItemUnstuck.Logger.LogError((object)("[Events] OnBeforeRelocate handler error: " + ex.Message)); } } return true; } internal static void RaiseItemRelocated(ValuableObject valuable, Vector3 from, Vector3 to) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (ItemUnstuckEvents.OnItemRelocated == null) { return; } Delegate[] invocationList = ItemUnstuckEvents.OnItemRelocated.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<ValuableObject, Vector3, Vector3> action = (Action<ValuableObject, Vector3, Vector3>)invocationList[i]; try { action(valuable, from, to); } catch (Exception ex) { ItemUnstuck.Logger.LogError((object)("[Events] OnItemRelocated handler error: " + ex.Message)); } } } internal static void RaiseMidGameSpawnDetected(ValuableObject valuable) { if (ItemUnstuckEvents.OnMidGameSpawnDetected == null) { return; } Delegate[] invocationList = ItemUnstuckEvents.OnMidGameSpawnDetected.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<ValuableObject> action = (Action<ValuableObject>)invocationList[i]; try { action(valuable); } catch (Exception ex) { ItemUnstuck.Logger.LogError((object)("[Events] OnMidGameSpawnDetected handler error: " + ex.Message)); } } } internal static void RaiseRelocationFailed(ValuableObject valuable, string reason) { if (ItemUnstuckEvents.OnRelocationFailed == null) { return; } Delegate[] invocationList = ItemUnstuckEvents.OnRelocationFailed.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<ValuableObject, string> action = (Action<ValuableObject, string>)invocationList[i]; try { action(valuable, reason); } catch (Exception ex) { ItemUnstuck.Logger.LogError((object)("[Events] OnRelocationFailed handler error: " + ex.Message)); } } } } internal readonly struct MotionStats { public float AverageSpeed { get; } public float MaxSpeed { get; } public int DirectionReversals { get; } public float ReversalDotThreshold { get; } public MotionStats(float averageSpeed, float maxSpeed, int directionReversals, float reversalDotThreshold) { AverageSpeed = averageSpeed; MaxSpeed = maxSpeed; DirectionReversals = directionReversals; ReversalDotThreshold = reversalDotThreshold; } public override string ToString() { return $"avgSpeed={AverageSpeed:0.000}m/s maxSpeed={MaxSpeed:0.000}m/s reversals={DirectionReversals} dotTh={ReversalDotThreshold:0.00}"; } } internal readonly struct RelocatedValuableInfo { public string Name { get; } public Type Type { get; } public float ValueCurrentAtRelocation { get; } public float? BaselineCurrent { get; } public Vector3 From { get; } public Vector3 To { get; } public RelocatedValuableInfo(string name, Type type, float valueCurrentAtRelocation, float? baselineCurrent, Vector3 from, Vector3 to) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Name = name; Type = type; ValueCurrentAtRelocation = valueCurrentAtRelocation; BaselineCurrent = baselineCurrent; From = from; To = to; } } internal static class RelocationAnalytics { private static int totalRelocations; private static int successfulRelocations; private static int fallbackRelocations; private static int failedRelocations; private static int midGameRelocations; private static int predictiveRelocations; private static int skippedItems; private static DateTime sessionStart = DateTime.Now; private static string currentLevel = "Unknown"; private static int levelLoadCount; private static readonly string LogDirectory = Path.Combine(Paths.PluginPath, "ItemUnstuck", "logs"); private static readonly string LogFilePath = Path.Combine(LogDirectory, "itemunstuck.log"); private static readonly string OldLogFilePath = Path.Combine(LogDirectory, "itemunstuck.old.log"); private const long MAX_LOG_SIZE_BYTES = 5242880L; private const int MAX_LOG_AGE_HOURS = 24; private static StreamWriter? logWriter; private static DateTime lastRotationCheck = DateTime.MinValue; private static readonly object writeLock = new object(); private static bool initialized; internal static void Initialize() { if (initialized) { return; } try { if (!Directory.Exists(LogDirectory)) { Directory.CreateDirectory(LogDirectory); } RotateLogIfNeeded(); logWriter = new StreamWriter(LogFilePath, append: true, Encoding.UTF8) { AutoFlush = true }; initialized = true; WriteToLog("========================================"); WriteToLog($"SESSION START: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); WriteToLog("ItemUnstuck Diagnostics Log"); WriteToLog("Unity: " + Application.unityVersion + " | Game: " + Application.version); WriteToLog("========================================"); WriteConfig(); } catch (Exception ex) { ItemUnstuck.Logger.LogError((object)("[Diagnostics] Failed to initialize logging: " + ex.Message)); } } internal static void Shutdown() { try { if (logWriter != null) { WriteToLog($"SESSION END: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); WriteToLog("Stats: " + GetSummary()); WriteToLog("========================================\n"); logWriter.Close(); logWriter = null; } } catch { } initialized = false; } private static void RotateLogIfNeeded() { try { if (!File.Exists(LogFilePath)) { return; } FileInfo fileInfo = new FileInfo(LogFilePath); bool flag = false; if (fileInfo.Length > 5242880) { flag = true; } if ((DateTime.Now - fileInfo.LastWriteTime).TotalHours > 24.0) { flag = true; } if (flag) { if (File.Exists(OldLogFilePath)) { File.Delete(OldLogFilePath); } File.Move(LogFilePath, OldLogFilePath); } } catch (Exception ex) { ItemUnstuck.Logger.LogWarning((object)("[Diagnostics] Log rotation failed: " + ex.Message)); } } private static void WriteToLog(string message) { if (logWriter == null) { return; } lock (writeLock) { try { logWriter.WriteLine(message); if ((DateTime.Now - lastRotationCheck).TotalMinutes > 5.0) { lastRotationCheck = DateTime.Now; if (logWriter.BaseStream.Length > 5242880) { logWriter.Close(); RotateLogIfNeeded(); logWriter = new StreamWriter(LogFilePath, append: true, Encoding.UTF8) { AutoFlush = true }; } } } catch { } } } private static void WriteConfig() { try { WriteToLog("[CONFIG]"); WriteToLog($" Enable={ConfigEntries.Enable.Value}"); WriteToLog($" EnableRelocation={ConfigEntries.EnableRelocation.Value}"); WriteToLog($" EnableMidGameDetection={ConfigEntries.EnableMidGameDetection}"); WriteToLog($" EnableContainerDetection={ConfigEntries.EnableContainerDetection}"); WriteToLog($" UseFallbackPositions={ConfigEntries.UseFallbackPositions}"); WriteToLog($" InvulnerableUntilGrab={ConfigEntries.InvulnerableUntilGrab.Value}"); WriteToLog($" ScanDelaySeconds={ConfigEntries.ScanDelaySeconds}"); WriteToLog($" MidGameScanDelaySeconds={ConfigEntries.MidGameScanDelaySeconds}"); WriteToLog($" StuckMinPenetrationDistance={ConfigEntries.StuckMinPenetrationDistance}"); WriteToLog($" VerboseLogging={ConfigEntries.VerboseLogging.Value}"); } catch { } } internal static void LogRelocation(string itemName, Vector3 from, Vector3 to, string reason, bool usedFallback, bool isMidGame, bool isPredictive) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) totalRelocations++; successfulRelocations++; if (usedFallback) { fallbackRelocations++; } if (isMidGame) { midGameRelocations++; } if (isPredictive) { predictiveRelocations++; } List<string> list = new List<string>(); if (usedFallback) { list.Add("FALLBACK"); } if (isMidGame) { list.Add("MIDGAME"); } if (isPredictive) { list.Add("PREDICTIVE"); } string text = ((list.Count > 0) ? (" [" + string.Join(",", list) + "]") : ""); WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] RELOCATED{text} '{itemName}' from={from:F2} to={to:F2} reason='{reason}'"); } internal static void LogFailure(string itemName, string reason, Vector3? position = null) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) totalRelocations++; failedRelocations++; string text = (position.HasValue ? $" pos={position.Value:F2}" : ""); WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] FAILED '{itemName}'{text} reason='{reason}'"); } internal static void LogSkipped(string itemName, string reason, Vector3? position = null) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) skippedItems++; if (ConfigEntries.VerboseLogging.Value) { string text = (position.HasValue ? $" pos={position.Value:F2}" : ""); WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] SKIPPED '{itemName}'{text} reason='{reason}'"); } } internal static void LogEmbedment(string itemName, float penetrationDepth, bool isWorldEmbedded, Vector3 position) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (ConfigEntries.VerboseLogging.Value) { WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] EMBEDMENT '{itemName}' pos={position:F2} depth={penetrationDepth:F3}m worldEmbedded={isWorldEmbedded}"); } } internal static void SetLevel(string levelName) { currentLevel = levelName ?? "Unknown"; levelLoadCount++; WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] LEVEL_CHANGE '{currentLevel}' (load #{levelLoadCount})"); } internal static void LogWarning(string message) { WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] WARNING {message}"); } internal static void LogError(string message) { WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] ERROR {message}"); } internal static void LogInfo(string message) { if (ConfigEntries.VerboseLogging.Value) { WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] INFO {message}"); } } internal static void ResetSession() { totalRelocations = 0; successfulRelocations = 0; fallbackRelocations = 0; failedRelocations = 0; midGameRelocations = 0; predictiveRelocations = 0; skippedItems = 0; sessionStart = DateTime.Now; levelLoadCount = 0; currentLevel = "Unknown"; WriteToLog($"[{DateTime.Now:HH:mm:ss.fff}] SESSION_RESET"); } internal static string GetSummary() { return $"Relocations: {successfulRelocations}/{totalRelocations} (fallback:{fallbackRelocations} failed:{failedRelocations} midGame:{midGameRelocations} predictive:{predictiveRelocations} skipped:{skippedItems})"; } internal static string GetLogFilePath() { return LogFilePath; } } internal class RelocationRecord { public ValuableObject ValuableRef; public string ItemName = ""; public Vector3 BeforePos; public Vector3 AfterPos; public Quaternion AfterRot; public string Reason = ""; public string Time = ""; public bool IsPending; public bool IsCompleted; } internal static class RelocationHistory { private const int MaxRecords = 20; public static readonly List<RelocationRecord> Records = new List<RelocationRecord>(); public static void Add(ValuableObject val, Vector3 before, Vector3 after, Quaternion rot, string reason, bool pending) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_002d: Unknown result type (might be due to invalid IL or missing references) Records.Add(new RelocationRecord { ValuableRef = val, ItemName = ((Object)val).name, BeforePos = before, AfterPos = after, AfterRot = rot, Reason = reason, Time = DateTime.Now.ToString("HH:mm:ss"), IsPending = pending, IsCompleted = !pending }); if (Records.Count > 20) { Records.RemoveAt(0); } } public static void Clear() { Records.Clear(); } } internal static class SpawnModIntegration { private static bool isBlindBoxDetected; private static bool checkedForBlindBox; internal static bool IsBlindBoxInstalled() { if (checkedForBlindBox) { return isBlindBoxDetected; } checkedForBlindBox = true; try { isBlindBoxDetected = AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.FullName.Contains("BlindBox", StringComparison.OrdinalIgnoreCase)); if (isBlindBoxDetected) { ItemUnstuck.Logger.LogInfo((object)"[SpawnModIntegration] BlindBox mod detected - enhanced spawn handling enabled"); } } catch (Exception ex) { ItemUnstuck.Logger.LogWarning((object)("[SpawnModIntegration] Error detecting BlindBox: " + ex.Message)); isBlindBoxDetected = false; } return isBlindBoxDetected; } internal static bool IsModSpawnedItem(ValuableObject valuable) { if ((Object)(object)valuable == (Object)null) { return false; } string name = ((Object)valuable).name; if (string.IsNullOrEmpty(name)) { return false; } if (name.Contains("BlindBox", StringComparison.OrdinalIgnoreCase) || name.Contains("Spawned", StringComparison.OrdinalIgnoreCase) || name.Contains("Custom", StringComparison.OrdinalIgnoreCase)) { return true; } Transform parent = ((Component)valuable).transform.parent; while ((Object)(object)parent != (Object)null) { string name2 = ((Object)parent).name; if (name2.Contains("BlindBox", StringComparison.OrdinalIgnoreCase) || name2.Contains("ModSpawn", StringComparison.OrdinalIgnoreCase)) { return true; } parent = parent.parent; } return false; } internal static float GetRecommendedDelay(ValuableObject valuable) { if (IsModSpawnedItem(valuable)) { return ConfigEntries.MidGameScanDelaySeconds * 1.5f; } return ConfigEntries.MidGameScanDelaySeconds; } internal static void Reset() { checkedForBlindBox = false; isBlindBoxDetected = false; } } internal static class SpawnSlotRegistry { internal readonly struct Slot { public Vector3 Position { get; } public Quaternion Rotation { get; } public Type Type { get; } public Transform? Parent { get; } public Slot(Vector3 position, Quaternion rotation, Type type, Transform? parent) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) Position = position; Rotation = rotation; Type = type; Parent = parent; } } private static readonly List<Slot> SlotsInternal = new List<Slot>(); public static IReadOnlyList<Slot> Slots => SlotsInternal; public static int GetSlotCount() { return SlotsInternal.Count; } public static void Clear() { SlotsInternal.Clear(); } public static void Register(ValuableVolume volume) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)volume).transform.position; Quaternion rotation = ((Component)volume).transform.rotation; for (int i = 0; i < SlotsInternal.Count; i++) { Slot slot = SlotsInternal[i]; if (slot.Type == volume.VolumeType && Vector3.SqrMagnitude(slot.Position - position) < 0.0001f) { return; } } SlotsInternal.Add(new Slot(position, rotation, volume.VolumeType, ((Component)volume).transform.parent)); } } internal static class StuckValuableRelocator { internal readonly struct EmbedmentAssessment { public float StaticPenetrationSum { get; } public float StaticPenetrationMax { get; } public float StaticPenetrationMaxUpDot { get; } public Vector3 StaticPenetrationMaxDirection { get; } public float DynamicPenetrationSum { get; } public float DynamicPenetrationMax { get; } public float DynamicPenetrationMaxUpDot { get; } public bool MidpointInsideStatic { get; } public bool MidpointInsideDynamic { get; } public bool CenterInsideStatic { get; } public float MinDimension { get; } public float EmbedRatio { get; } public Vector3 BoundsSize { get; } public EmbedmentAssessment(float staticPenetrationSum, float staticPenetrationMax, float staticPenetrationMaxUpDot, Vector3 staticPenetrationMaxDirection, float dynamicPenetrationSum, float dynamicPenetrationMax, float dynamicPenetrationMaxUpDot, bool midpointInsideStatic, bool midpointInsideDynamic, bool centerInsideStatic, float minDimension, float embedRatio, Vector3 boundsSize) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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) StaticPenetrationSum = staticPenetrationSum; StaticPenetrationMax = staticPenetrationMax; StaticPenetrationMaxUpDot = staticPenetrationMaxUpDot; StaticPenetrationMaxDirection = staticPenetrationMaxDirection; DynamicPenetrationSum = dynamicPenetrationSum; DynamicPenetrationMax = dynamicPenetrationMax; DynamicPenetrationMaxUpDot = dynamicPenetrationMaxUpDot; MidpointInsideStatic = midpointInsideStatic; MidpointInsideDynamic = midpointInsideDynamic; CenterInsideStatic = centerInsideStatic; MinDimension = minDimension; EmbedRatio = embedRatio; BoundsSize = boundsSize; } public override string ToString() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) return $"staticMax={StaticPenetrationMax:0.000}m upDot={StaticPenetrationMaxUpDot:0.00} staticSum={StaticPenetrationSum:0.000}m dynamicMax={DynamicPenetrationMax:0.000}m upDotDyn={DynamicPenetrationMaxUpDot:0.00} dynamicSum={DynamicPenetrationSum:0.000}m midInStatic={MidpointInsideStatic} centerInStatic={CenterInsideStatic} midInDyn={MidpointInsideDynamic} minDim={MinDimension:0.000}m ratio={EmbedRatio:P1} size={BoundsSize}"; } } public readonly struct ScanResult { public List<RelocatedValuableInfo> Relocated { get; } public ScanResult(List<RelocatedValuableInfo> relocated) { Relocated = relocated; } } private static readonly Collider[] OverlapBuffer = (Collider[])(object)new Collider[128]; private static readonly Collider[] CenterCheckBuffer = (Collider[])(object)new Collider[32]; private static readonly Collider[] SlotCheckBuffer = (Collider[])(object)new Collider[64]; private static readonly Dictionary<int, Rigidbody> RigidbodyCache = new Dictionary<int, Rigidbody>(64); private static Rigidbody? GetCachedRigidbody(ValuableObject valuable) { if ((Object)(object)valuable == (Object)null) { return null; } int instanceID = ((Object)valuable).GetInstanceID(); if (RigidbodyCache.TryGetValue(instanceID, out Rigidbody value)) { if ((Object)(object)value != (Object)null) { return value; } RigidbodyCache.Remove(instanceID); } Rigidbody component = ((Component)valuable).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { RigidbodyCache[instanceID] = component; } return component; } internal static void ClearCache() { RigidbodyCache.Clear(); } public static ScanResult RelocateWorldEmbedded(ValuableObject[] allValuables, IReadOnlyDictionary<int, MotionStats>? motionStats) { return RelocateCandidatesInternal(allValuables, allValuables, motionStats, "world-embedded"); } internal static bool TryRecoverFallThrough(ValuableObject valuable) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) if (!ConfigEntries.Enable.Value || !ConfigEntries.EnableRelocation.Value) { return false; } if ((Object)(object)GameManager.instance == (Object)null) { return false; } if (GameManager.instance.gameMode == 1 && !PhotonNetwork.IsMasterClient) { return false; } if ((Object)(object)valuable == (Object)null) { return false; } PhysGrabObject physGrabObject = valuable.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { return false; } if ((Object)(object)physGrabObject.impactDetector != (Object)null && physGrabObject.impactDetector.inCart) { return false; } IReadOnlyList<SpawnSlotRegistry.Slot> slots = SpawnSlotRegistry.Slots; if (slots.Count == 0) { return false; } ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>() ?? Array.Empty<ValuableObject>(); float num = Mathf.Max(0.05f, ConfigEntries.OccupiedDistance); float num2 = num * num; SpawnSlotRegistry.Slot? slot = FindFreeSlot(valuable.volumeType, array, slots, num2, valuable); if (!slot.HasValue) { for (int i = 0; i < slots.Count; i++) { SpawnSlotRegistry.Slot slot2 = slots[i]; if (!IsSlotClearOfWorldGeometry(slot2)) { continue; } bool flag = false; foreach (ValuableObject val in array) { if (!((Object)(object)val == (Object)null) && Vector3.SqrMagnitude(((Component)val).transform.position - slot2.Position) <= num2) { flag = true; break; } } if (!flag) { slot = slot2; break; } } } if (!slot.HasValue) { return false; } SpawnSlotRegistry.Slot value = slot.Value; physGrabObject.Teleport(value.Position, value.Rotation); InvulnerabilityController.ApplyUntilGrab(physGrabObject); Rigidbody component = ((Component)valuable).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { component.velocity = Vector3.zero; component.angularVelocity = Vector3.zero; } if ((Object)(object)value.Parent != (Object)null) { ((Component)valuable).transform.parent = value.Parent; } RelocationHistory.Add(valuable, ((Component)valuable).transform.position, value.Position, value.Rotation, "fall-through recovery", pending: false); ItemUnstuck.Logger.LogInfo((object)("[ItemUnstuck] Fall-through detected and recovered for '" + ((Object)valuable).name + "'.")); return true; } internal static bool ShouldSkipEmbedmentCheck(ValuableObject valuable, out string skipReason) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) skipReason = ""; if ((Object)(object)valuable == (Object)null) { skipReason = "item is null"; return true; } if (IsExcluded(valuable)) { skipReason = "explicitly excluded (e.g. enemy drop)"; return true; } Rigidbody component = ((Component)valuable).GetComponent<Rigidbody>(); if ((Object)(object)component == (Object)null) { skipReason = "no rigidbody"; return true; } Vector3 velocity = component.velocity; float magnitude = ((Vector3)(ref velocity)).magnitude; if (magnitude > 2f) { skipReason = $"moving fast ({magnitude:F2} m/s)"; return true; } Vector3 position = ((Component)valuable).transform.position; int mask = LayerMask.GetMask(new string[1] { "Default" }); RaycastHit val = default(RaycastHit); if (!Physics.Raycast(position, Vector3.down, ref val, 5f, mask)) { skipReason = "far from ground (>5m)"; return true; } return false; } internal static bool IsExcluded(ValuableObject valuable) { if ((Object)(object)valuable == (Object)null) { return true; } if (((Object)valuable).name.StartsWith("Enemy Valuable")) { return true; } return false; } private static Vector3? FindFallbackSpawnPosition(ValuableObject valuable) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0227: 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_0070: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)valuable == (Object)null) { return null; } Vector3 position = ((Component)valuable).transform.position; int mask = LayerMask.GetMask(new string[1] { "Default" }); PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>(); if (array.Length != 0) { PlayerAvatar val = null; float num = float.MaxValue; PlayerAvatar[] array2 = array; foreach (PlayerAvatar val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { float num2 = Vector3.SqrMagnitude(((Component)val2).transform.position - position); if (num2 < num) { num = num2; val = val2; } } } if ((Object)(object)val != (Object)null) { Vector3 position2 = ((Component)val).transform.position; Vector3 forward = ((Component)val).transform.forward; RaycastHit val3 = default(RaycastHit); if (Physics.Raycast(position2 + forward * 2f + Vector3.up * 2f, Vector3.down, ref val3, 5f, mask)) { Vector3 val4 = ((RaycastHit)(ref val3)).point + Vector3.up * 0.5f; if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)$"[Fallback] Using player-relative position: {val4}"); } return val4; } } } Vector3 val5 = position + Vector3.up * 5f; int num3 = ~LayerMask.GetMask(new string[6] { "Player", "Valuable", "Enemy", "Triggers", "PlayerOnlyCollision", "UI" }); if (Physics.OverlapSphereNonAlloc(val5, 0.5f, CenterCheckBuffer, num3, (QueryTriggerInteraction)1) == 0) { if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)$"[Fallback] Using vertical offset position: {val5}"); } return val5; } RaycastHit val6 = default(RaycastHit); if (Physics.Raycast(val5, Vector3.down, ref val6, 10f, mask)) { Vector3 val7 = ((RaycastHit)(ref val6)).point + Vector3.up * 0.5f; if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)$"[Fallback] Using ground below vertical: {val7}"); } return val7; } if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogWarning((object)$"[Fallback] Using last-resort vertical position: {val5}"); } return val5; } internal static bool WillEmbedSoon(ValuableObject valuable, out float predictedDepth, out string reason) { //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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) predictedDepth = 0f; reason = ""; if ((Object)(object)valuable == (Object)null) { return false; } Rigidbody component = ((Component)valuable).GetComponent<Rigidbody>(); if ((Object)(object)component == (Object)null) { return false; } Vector3 val = component.velocity; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.5f) { return false; } Vector3 position = ((Component)valuable).transform.position; Vector3 velocity = component.velocity; Vector3 val2 = position + velocity * 0.3f; int num = ~LayerMask.GetMask(new string[6] { "Player", "Valuable", "Enemy", "Triggers", "PlayerOnlyCollision", "UI" }); if (Physics.OverlapSphereNonAlloc(val2, 0.3f, CenterCheckBuffer, num, (QueryTriggerInteraction)1) > 0) { val = val2 - position; Vector3 normalized = ((Vector3)(ref val)).normalized; float num2 = Vector3.Distance(position, val2); RaycastHit val3 = default(RaycastHit); if (Physics.Raycast(position, normalized, ref val3, num2, num)) { float num3 = num2 - ((RaycastHit)(ref val3)).distance; if (num3 > 0.1f) { predictedDepth = num3; reason = $"trajectory predicts {num3:F2}m embed at {magnitude:F1} m/s"; return true; } } } return false; } internal static bool TryRelocateSingleMidGameSpawn(ValuableObject valuable, ValuableObject[] allValuables) { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_0370: Unknown result type (might be due to invalid IL or missing references) //IL_0377: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_03d8: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0274: 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_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) if (!ConfigEntries.Enable.Value || !ConfigEntries.EnableRelocation.Value || !ConfigEntries.EnableMidGameDetection.Value) { return false; } if ((Object)(object)valuable == (Object)null) { return false; } PhysGrabObject physGrabObject = valuable.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { return false; } if ((Object)(object)physGrabObject.impactDetector != (Object)null && physGrabObject.impactDetector.inCart) { return false; } ComputeEmbedment(valuable); if (!ShouldRelocateBasedOnAssessment(valuable, null, out string reason)) { if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)("[ItemUnstuck][MidGame] '" + ((Object)valuable).name + "' is NOT stuck: " + reason)); } return false; } if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)("[ItemUnstuck][MidGame] '" + ((Object)valuable).name + "' is STUCK: " + reason)); } IReadOnlyList<SpawnSlotRegistry.Slot> slots = SpawnSlotRegistry.Slots; if (slots.Count == 0) { ItemUnstuck.Logger.LogWarning((object)("[ItemUnstuck][MidGame] No spawn slots available for relocating '" + ((Object)valuable).name + "'")); return false; } float num = Mathf.Max(0.05f, ConfigEntries.OccupiedDistance); float num2 = num * num; SpawnSlotRegistry.Slot? slot = FindFreeSlot(valuable.volumeType, allValuables, slots, num2, valuable); if (!slot.HasValue) { for (int i = 0; i < slots.Count; i++) { SpawnSlotRegistry.Slot slot2 = slots[i]; if (!IsSlotClearOfWorldGeometry(slot2)) { continue; } bool flag = false; foreach (ValuableObject val in allValuables) { if (!((Object)(object)val == (Object)null) && Vector3.SqrMagnitude(((Component)val).transform.position - slot2.Position) <= num2) { flag = true; break; } } if (!flag) { slot = slot2; break; } } } if (!slot.HasValue) { if (!ConfigEntries.UseFallbackPositions) { ItemUnstuck.Logger.LogWarning((object)("[ItemUnstuck][MidGame] No free slot available for relocating stuck item '" + ((Object)valuable).name + "'")); return false; } Vector3? val2 = FindFallbackSpawnPosition(valuable); if (!val2.HasValue) { ItemUnstuck.Logger.LogError((object)("[ItemUnstuck][MidGame] No fallback position found for '" + ((Object)valuable).name + "' - item remains stuck!")); return false; } Vector3 position = ((Component)valuable).transform.position; physGrabObject.Teleport(val2.Value, ((Component)valuable).transform.rotation); InvulnerabilityController.ApplyUntilGrab(physGrabObject); Rigidbody component = ((Component)valuable).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { component.velocity = Vector3.zero; component.angularVelocity = Vector3.zero; } RelocationHistory.Add(valuable, position, val2.Value, ((Component)valuable).transform.rotation, "mid-game stuck (fallback position)", pending: false); FallThroughRecovery.WatchRelocated(valuable, "mid-game fallback relocation"); if (ConfigEntries.MidGameHudNotifications && ConfigEntries.HudNotifications.Value) { HudNotifier.ShowMidGameRelocation(((Object)valuable).name ?? "Unknown item"); } ItemUnstuck.Logger.LogInfo((object)$"[ItemUnstuck][MidGame] Relocated '{((Object)valuable).name}' to fallback position from {position} to {val2.Value}"); return true; } Vector3 position2 = ((Component)valuable).transform.position; SpawnSlotRegistry.Slot value = slot.Value; physGrabObject.Teleport(value.Position, value.Rotation); InvulnerabilityController.ApplyUntilGrab(physGrabObject); Rigidbody component2 = ((Component)valuable).GetComponent<Rigidbody>(); if ((Object)(object)component2 != (Object)null) { component2.velocity = Vector3.zero; component2.angularVelocity = Vector3.zero; } if ((Object)(object)value.Parent != (Object)null) { ((Component)valuable).transform.parent = value.Parent; } RelocationHistory.Add(valuable, position2, value.Position, value.Rotation, "mid-game stuck: " + reason, pending: false); FallThroughRecovery.WatchRelocated(valuable, "mid-game relocation"); if (ConfigEntries.MidGameHudNotifications && ConfigEntries.HudNotifications.Value) { HudNotifier.ShowMidGameRelocation(((Object)valuable).name ?? "Unknown item"); } ItemUnstuck.Logger.LogInfo((object)$"[ItemUnstuck][MidGame] Relocated stuck mid-game spawn '{((Object)valuable).name}' from {position2} to {value.Position}"); return true; } public static ScanResult RelocateCandidates(IEnumerable<ValuableObject> candidates, ValuableObject[] allValuables, IReadOnlyDictionary<int, MotionStats>? motionStats) { return RelocateCandidatesInternal(candidates, allValuables, motionStats, "damaged"); } private static ScanResult RelocateCandidatesInternal(IEnumerable<ValuableObject> candidates, ValuableObject[] allValuables, IReadOnlyDictionary<int, MotionStats>? motionStats, string label) { //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_0372: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_037c: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_03f7: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) if (!ConfigEntries.Enable.Value || !ConfigEntries.EnableRelocation.Value) { return new ScanResult(new List<RelocatedValuableInfo>()); } if ((Object)(object)GameManager.instance == (Object)null) { return new ScanResult(new List<RelocatedValuableInfo>()); } if (GameManager.instance.gameMode == 1 && !PhotonNetwork.IsMasterClient) { return new ScanResult(new List<RelocatedValuableInfo>()); } IReadOnlyList<SpawnSlotRegistry.Slot> slots = SpawnSlotRegistry.Slots; if (slots.Count == 0) { ItemUnstuck.Logger.LogWarning((object)"No ValuableVolume spawn slots were captured; cannot relocate stuck valuables."); return new ScanResult(new List<RelocatedValuableInfo>()); } if (candidates == null) { return new ScanResult(new List<RelocatedValuableInfo>()); } if (allValuables == null || allValuables.Length == 0) { allValuables = Object.FindObjectsOfType<ValuableObject>() ?? Array.Empty<ValuableObject>(); } float num = Mathf.Max(0.05f, ConfigEntries.OccupiedDistance); float occupiedDistanceSqr = num * num; List<RelocatedValuableInfo> list = new List<RelocatedValuableInfo>(); int num2 = 0; HashSet<Rigidbody> hashSet = new HashSet<Rigidbody>(); foreach (ValuableObject candidate in candidates) { if ((Object)(object)candidate == (Object)null) { continue; } PhysGrabObject physGrabObject = candidate.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { continue; } if (IsExcluded(candidate)) { _ = ConfigEntries.VerboseLogging.Value; num2++; } else { if ((Object)(object)physGrabObject.impactDetector != (Object)null && physGrabObject.impactDetector.inCart) { continue; } Rigidbody component = ((Component)candidate).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { if (hashSet.Contains(component)) { continue; } hashSet.Add(component); } MotionStats? motion = null; if (motionStats != null) { int id = ValueDiagnostics.GetId(candidate); if (motionStats.TryGetValue(id, out var value)) { motion = value; } } if (IsReachableBySight(candidate)) { num2++; if (!ConfigEntries.VerboseLogging.Value) { } continue; } if (!ShouldRelocateBasedOnAssessment(candidate, motion, out string reason)) { num2++; if (!ConfigEntries.VerboseLogging.Value) { } continue; } if (ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)("[ItemUnstuck] Item FLAGGED AS STUCK '" + ((Object)candidate).name + "': " + reason)); } Type volumeType = candidate.volumeType; SpawnSlotRegistry.Slot? slot = FindFreeSlot(volumeType, allValuables, slots, occupiedDistanceSqr, candidate); if (!slot.HasValue && (Object)(object)candidate != (Object)null) { slot = FindSharedSlot(volumeType, allValuables, slots, candidate); if (slot.HasValue && ConfigEntries.VerboseLogging.Value) { ItemUnstuck.Logger.LogInfo((object)("[ItemUnstuck] Using slot sharing f