using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; 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 HowToFish.ItemRetriever.Config; using HowToFish.ItemRetriever.Models; using HowToFish.ItemRetriever.Systems; using HowToFish.ItemRetriever.Utils; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("ItemRetriever")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ItemRetriever")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("D181CDA7-EF07-4BBC-B975-2B80FC6BBFAE")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace HowToFish.ItemRetriever { [BepInPlugin("odinplus.itemretriever", "Item Retriever", "1.0.1")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "odinplus.itemretriever"; public const string PluginName = "Item Retriever"; public const string PluginVersion = "1.0.1"; private Harmony _harmony; private GameBridge _bridge; private ItemFinder _finder; private RetrieverLogic _logic; private NetworkSync _network; private IDisposable _consoleRegistration; private float _nextConsoleAttempt; private float _cooldownUntil; private bool _loggedReady; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; RetrieverConfig.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.Save(); _bridge = new GameBridge(((BaseUnityPlugin)this).Logger); _network = new NetworkSync(((BaseUnityPlugin)this).Logger, _bridge); _finder = new ItemFinder(((BaseUnityPlugin)this).Logger, _bridge); _logic = new RetrieverLogic(((BaseUnityPlugin)this).Logger, _bridge, _finder, _network); _harmony = new Harmony("odinplus.itemretriever"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); TryRegisterConsoleCommand(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Hotkey: {2}.", "Item Retriever", "1.0.1", RetrieverConfig.RetrieveKey.Value)); } private void Update() { //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (_consoleRegistration == null && Time.unscaledTime >= _nextConsoleAttempt) { _nextConsoleAttempt = Time.unscaledTime + 2f; TryRegisterConsoleCommand(); } if (RetrieverConfig.Enabled.Value) { if (!_loggedReady && _bridge.GetLocalPlayer() != null) { _loggedReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Local player found. Item Retriever is ready."); } if (!ShouldIgnoreHotkey() && Input.GetKeyDown(RetrieverConfig.RetrieveKey.Value)) { RunRetrieve("hotkey"); } } } private void OnDestroy() { _consoleRegistration?.Dispose(); _consoleRegistration = null; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (Instance == this) { Instance = null; } } internal RetrieveResult RunRetrieve(string source) { if (!RetrieverConfig.Enabled.Value) { return Fail("Item Retriever is disabled in config."); } if (Time.unscaledTime < _cooldownUntil) { float num = _cooldownUntil - Time.unscaledTime; return Fail($"On cooldown ({num:0.0}s left)."); } object localPlayer = _bridge.GetLocalPlayer(); if (localPlayer == null) { return Fail("No local player yet. Load into an island first."); } if (RetrieverConfig.HostOnly.Value && !_network.IsServer) { return Fail("Only the lobby host can retrieve items."); } if (!_network.CanRetrieve) { return Fail("This client does not have authority to move world items. Ask the host, or disable HostOnly if you own the items."); } RetrieveResult retrieveResult = _logic.TryRetrieve(localPlayer, source); if (retrieveResult.Success) { float num2 = Mathf.Max(0f, RetrieverConfig.CooldownSeconds.Value); if (num2 > 0f) { _cooldownUntil = Time.unscaledTime + num2; } } Feedback(retrieveResult); return retrieveResult; } private bool ShouldIgnoreHotkey() { if (RetrieverConfig.IgnoreWhenChatOpen.Value && _bridge.IsChatOrConsoleBlocking()) { return true; } try { object localPlayer = _bridge.GetLocalPlayer(); if (localPlayer != null && _bridge.IsPlayerBlockingInputs(localPlayer)) { return true; } } catch { } return false; } private void Feedback(RetrieveResult result) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) string text = result.Message ?? (result.Success ? "Items retrieved." : "Nothing to retrieve."); if (RetrieverConfig.LogToConsole.Value) { if (result.Success) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)text); } } if (RetrieverConfig.ShowChatMessage.Value) { _bridge.TrySendLocalChat(text); } if (result.Success && RetrieverConfig.ScreenShake.Value) { _bridge.TryShake(0.25f, 8, new Vector2(0.12f, 0.12f)); } WriteModConsole(text, result.Success ? "info" : "warn"); } private RetrieveResult Fail(string message) { RetrieveResult result = RetrieveResult.Failed(message); Feedback(result); return result; } private void TryRegisterConsoleCommand() { if (_consoleRegistration != null) { return; } try { MethodInfo methodInfo = Type.GetType("HowToFish.ModConsole.ModConsoleApi, HowToFish.ModConsole", throwOnError: false)?.GetMethod("RegisterCommand", BindingFlags.Static | BindingFlags.Public, null, new Type[6] { typeof(string), typeof(string), typeof(string), typeof(string), typeof(Func), typeof(string[]) }, null); if (!(methodInfo == null)) { Func func = ExecuteConsoleCommand; _consoleRegistration = methodInfo.Invoke(null, new object[6] { "odinplus.itemretriever", "retrieve", "Pull lost weapons and items back to you.", "retrieve [status|now]", func, new string[3] { "retriever", "getitem", "bringitems" } }) as IDisposable; if (_consoleRegistration != null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Registered retrieve command with Mod Console."); } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Mod Console not available yet: " + ex.Message)); } } private string ExecuteConsoleCommand(string[] args) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) string text = ((args != null && args.Length != 0) ? args[0].Trim() : "now"); if (text.Equals("status", StringComparison.OrdinalIgnoreCase) || text.Equals("help", StringComparison.OrdinalIgnoreCase)) { object localPlayer = _bridge.GetLocalPlayer(); int num = ((localPlayer != null) ? _finder.CountCandidates(_bridge.GetPlayerPosition(localPlayer)) : 0); float num2 = Mathf.Max(0f, _cooldownUntil - Time.unscaledTime); return string.Format("Item Retriever {0} | enabled={1} | ", "1.0.1", RetrieverConfig.Enabled.Value) + $"host={_network.IsServer} | canRetrieve={_network.CanRetrieve} | " + $"candidates={num} | cooldown={num2:0.0}s | key={RetrieverConfig.RetrieveKey.Value}"; } RetrieveResult retrieveResult = RunRetrieve("console"); return retrieveResult.Message; } private static void WriteModConsole(string text, string level) { try { (Type.GetType("HowToFish.ModConsole.ModConsoleApi, HowToFish.ModConsole", throwOnError: false)?.GetMethod("WriteLine", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(string), typeof(string) }, null))?.Invoke(null, new object[2] { text, level }); } catch { } } } } namespace HowToFish.ItemRetriever.Utils { internal static class ReflectionUtil { public const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public static Type FindType(params string[] names) { foreach (string typeName in names) { Type type = Type.GetType(typeName, throwOnError: false); if (type != null) { return type; } } return null; } public static FieldInfo FindField(Type type, params string[] names) { if (type == null) { return null; } foreach (string name in names) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { foreach (string value in names) { if (fieldInfo.Name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return fieldInfo; } } } return (type.BaseType != null && type.BaseType != typeof(object)) ? FindField(type.BaseType, names) : null; } public static PropertyInfo FindProperty(Type type, params string[] names) { if (type == null) { return null; } foreach (string name in names) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property; } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { foreach (string value in names) { if (propertyInfo.Name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return propertyInfo; } } } return (type.BaseType != null && type.BaseType != typeof(object)) ? FindProperty(type.BaseType, names) : null; } public static MethodInfo FindMethod(Type type, string name, int parameterCount) { if (type == null) { return null; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == name && methodInfo.GetParameters().Length == parameterCount) { return methodInfo; } } return (type.BaseType != null && type.BaseType != typeof(object)) ? FindMethod(type.BaseType, name, parameterCount) : null; } public static object GetMember(object instance, MemberInfo member) { try { if (member is FieldInfo fieldInfo) { return fieldInfo.GetValue(fieldInfo.IsStatic ? null : instance); } if (member is PropertyInfo { CanRead: not false } propertyInfo) { MethodInfo getMethod = propertyInfo.GetGetMethod(nonPublic: true); bool flag = getMethod != null && getMethod.IsStatic; return propertyInfo.GetValue(flag ? null : instance, null); } } catch { return null; } return null; } public static bool SetMember(object instance, MemberInfo member, object value) { try { if (member is FieldInfo fieldInfo) { fieldInfo.SetValue(fieldInfo.IsStatic ? null : instance, ConvertTo(value, fieldInfo.FieldType)); return true; } if (member is PropertyInfo { CanWrite: not false } propertyInfo) { MethodInfo setMethod = propertyInfo.GetSetMethod(nonPublic: true); bool flag = setMethod != null && setMethod.IsStatic; propertyInfo.SetValue(flag ? null : instance, ConvertTo(value, propertyInfo.PropertyType), null); return true; } } catch { return false; } return false; } public static bool TryReadInt(object instance, MemberInfo member, out int value) { value = 0; object member2 = GetMember(instance, member); if (member2 == null) { return false; } try { value = Convert.ToInt32(member2, CultureInfo.InvariantCulture); return true; } catch { return false; } } public static bool TryReadFloat(object instance, MemberInfo member, out float value) { value = 0f; object member2 = GetMember(instance, member); if (member2 == null) { return false; } try { value = Convert.ToSingle(member2, CultureInfo.InvariantCulture); return true; } catch { return false; } } public static bool IsAlive(object value) { if (value == null) { return false; } Object val = (Object)((value is Object) ? value : null); if (val != null) { return Object.op_Implicit(val); } return true; } public static GameObject AsGameObject(object value) { GameObject val = (GameObject)((value is GameObject) ? value : null); if (val != null) { return val; } Component val2 = (Component)((value is Component) ? value : null); if (val2 != null) { return val2.gameObject; } return null; } public static IEnumerable SplitList(string raw) { if (string.IsNullOrEmpty(raw)) { yield break; } string[] parts = raw.Split(new char[3] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries); string[] array = parts; foreach (string part in array) { string trimmed = part.Trim(); if (trimmed.Length > 0) { yield return trimmed; } } } public static bool NameMatches(string haystack, IEnumerable needles) { if (string.IsNullOrEmpty(haystack)) { return false; } foreach (string needle in needles) { if (haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static object ConvertTo(object value, Type target) { if (value == null) { return null; } if (target.IsInstanceOfType(value)) { return value; } if (target.IsEnum) { return (value is string value2) ? Enum.Parse(target, value2, ignoreCase: true) : Enum.ToObject(target, Convert.ToInt64(value, CultureInfo.InvariantCulture)); } if (target == typeof(int)) { return Convert.ToInt32(value, CultureInfo.InvariantCulture); } if (target == typeof(float)) { return Convert.ToSingle(value, CultureInfo.InvariantCulture); } if (target == typeof(double)) { return Convert.ToDouble(value, CultureInfo.InvariantCulture); } return Convert.ChangeType(value, target, CultureInfo.InvariantCulture); } } } namespace HowToFish.ItemRetriever.Systems { public sealed class GameBridge { private readonly ManualLogSource _log; private float _nextRefresh; private bool _isCacheInitialized; private Type _playerType; private FieldInfo _localPlayerField; private PropertyInfo _blockInputsProperty; private PropertyInfo _holdingProperty; private PropertyInfo _screenShakeProperty; private PropertyInfo _heldItemProperty; private MethodInfo _shakeMethod; private Type _gameInfoType; private Type _waterManagerType; private MethodInfo _isUnderWaterMethod; private Type _instanceFinderType; private PropertyInfo _networkManagerProperty; private PropertyInfo _isServerStartedProperty; private PropertyInfo _isClientStartedProperty; private Type _chatManagerType; private MethodInfo _chatSendMethod; private FieldInfo _chatInstanceField; private MemberInfo _moneyMember; private object _moneyHost; private bool _moneyResolved; private Type _networkObjectType; private PropertyInfo _isOwnerProperty; private PropertyInfo _isServerInitializedProperty; public bool IsServer { get { EnsureCache(); object networkManager = GetNetworkManager(); if (networkManager == null) { return true; } CacheNetwork(networkManager.GetType()); return ReadBool(_isServerStartedProperty, networkManager); } } public bool IsClient { get { EnsureCache(); object networkManager = GetNetworkManager(); if (networkManager == null) { return false; } CacheNetwork(networkManager.GetType()); return ReadBool(_isClientStartedProperty, networkManager); } } public GameBridge(ManualLogSource log) { _log = log; } public object GetLocalPlayer() { EnsureCache(); try { object obj = _localPlayerField?.GetValue(null); return ReflectionUtil.IsAlive(obj) ? obj : null; } catch { return null; } } public Transform GetPlayerTransform(object player) { GameObject val = ReflectionUtil.AsGameObject(player); return Object.op_Implicit((Object)(object)val) ? val.transform : null; } public Transform GetPlayerCamTransform(object player) { if (player == null) { return null; } try { PropertyInfo property = player.GetType().GetProperty("CamObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value = property.GetValue(player, null); Transform val = (Transform)((value is Transform) ? value : null); if (val != null && Object.op_Implicit((Object)(object)val)) { return val; } GameObject val2 = (GameObject)((value is GameObject) ? value : null); if (val2 != null && Object.op_Implicit((Object)(object)val2)) { return val2.transform; } Component val3 = (Component)((value is Component) ? value : null); if (val3 != null && Object.op_Implicit((Object)(object)val3)) { return val3.transform; } } FieldInfo field = player.GetType().GetField("CamObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value2 = field.GetValue(player); Transform val4 = (Transform)((value2 is Transform) ? value2 : null); if (val4 != null && Object.op_Implicit((Object)(object)val4)) { return val4; } GameObject val5 = (GameObject)((value2 is GameObject) ? value2 : null); if (val5 != null && Object.op_Implicit((Object)(object)val5)) { return val5.transform; } Component val6 = (Component)((value2 is Component) ? value2 : null); if (val6 != null && Object.op_Implicit((Object)(object)val6)) { return val6.transform; } } } catch { } return GetPlayerTransform(player); } public Vector3 GetPlayerPosition(object player) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_003c: 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) Transform playerCamTransform = GetPlayerCamTransform(player); if (Object.op_Implicit((Object)(object)playerCamTransform)) { return playerCamTransform.position; } Transform playerTransform = GetPlayerTransform(player); return Object.op_Implicit((Object)(object)playerTransform) ? playerTransform.position : Vector3.zero; } public Vector3 GetPlayerForward(object player) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) Transform playerCamTransform = GetPlayerCamTransform(player); if (Object.op_Implicit((Object)(object)playerCamTransform)) { Vector3 forward = playerCamTransform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude > 0.001f) { return ((Vector3)(ref forward)).normalized; } } Transform playerTransform = GetPlayerTransform(player); if (!Object.op_Implicit((Object)(object)playerTransform)) { return Vector3.forward; } Vector3 forward2 = playerTransform.forward; forward2.y = 0f; return (((Vector3)(ref forward2)).sqrMagnitude > 0.001f) ? ((Vector3)(ref forward2)).normalized : Vector3.forward; } public object GetHeldItem(object player) { EnsureCache(); try { object obj = _holdingProperty?.GetValue(player, null); return (obj == null) ? null : _heldItemProperty?.GetValue(obj, null); } catch { return null; } } public bool IsPlayerBlockingInputs(object player) { EnsureCache(); try { bool flag = default(bool); int num; if (_blockInputsProperty != null) { object value = _blockInputsProperty.GetValue(player, null); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } public bool IsUnderWater(Vector3 position) { //IL_0046: 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) EnsureCache(); try { object obj = _isUnderWaterMethod?.Invoke(null, new object[1] { position }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return position.y < -1f; } } public bool IsLikelyLava(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (position.y > 2f) { return false; } Collider[] array = Physics.OverlapSphere(position, 1.2f, -1, (QueryTriggerInteraction)1); Collider[] array2 = array; foreach (Collider val in array2) { if (Object.op_Implicit((Object)(object)val)) { string name = ((Object)((Component)val).gameObject).name; if (name.IndexOf("lava", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("magma", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("volcano", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } return false; } public bool HasLineOfSight(Vector3 from, Vector3 to) { //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_0003: 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_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) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.25f) { return true; } return !Physics.Raycast(from + Vector3.up * 0.4f, ((Vector3)(ref val)).normalized, magnitude - 0.2f, -5, (QueryTriggerInteraction)1); } public bool IsNetworkObjectOwner(GameObject gameObject) { EnsureCache(); if (!Object.op_Implicit((Object)(object)gameObject) || _networkObjectType == null) { return false; } Component component = gameObject.GetComponent(_networkObjectType); if (!Object.op_Implicit((Object)(object)component)) { return false; } try { object obj = _isOwnerProperty?.GetValue(component, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } public bool IsNetworkObjectServerInit(GameObject gameObject) { EnsureCache(); if (!Object.op_Implicit((Object)(object)gameObject) || _networkObjectType == null || _isServerInitializedProperty == null) { return IsServer; } Component component = gameObject.GetComponent(_networkObjectType); if (!Object.op_Implicit((Object)(object)component)) { return IsServer; } try { object value = _isServerInitializedProperty.GetValue(component, null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return IsServer; } } public bool TryGetMoney(object player, out int money) { money = 0; EnsureMoney(player); return ReflectionUtil.TryReadInt(_moneyHost ?? player, _moneyMember, out money); } public bool TrySetMoney(object player, int money) { EnsureMoney(player); return _moneyMember != null && ReflectionUtil.SetMember(_moneyHost ?? player, _moneyMember, money); } public bool TryCharge(object player, int amount, out int charged, out string error) { charged = 0; error = null; if (amount <= 0) { return true; } if (!TryGetMoney(player, out var money)) { error = "Could not resolve wallet field."; _log.LogWarning((object)"Money member not resolved yet. Cost skipped."); return true; } if (money < amount) { error = $"Need ${amount}, you have ${money}."; return false; } if (!TrySetMoney(player, money - amount)) { error = "Failed to update balance."; return false; } charged = amount; return true; } public void TryShake(float intensity, int count, Vector2 range) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) EnsureCache(); object localPlayer = GetLocalPlayer(); if (localPlayer == null || _shakeMethod == null) { return; } try { object obj = _screenShakeProperty?.GetValue(localPlayer, null); if (obj != null) { _shakeMethod.Invoke(obj, new object[3] { intensity, count, range }); } } catch { } } public void TrySendLocalChat(string message) { EnsureCache(); if (string.IsNullOrEmpty(message)) { return; } try { object obj = _chatInstanceField?.GetValue(null); if (obj != null && _chatSendMethod != null) { ParameterInfo[] parameters = _chatSendMethod.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { _chatSendMethod.Invoke(obj, new object[1] { message }); return; } } } catch { } _log.LogInfo((object)message); } public bool IsChatOrConsoleBlocking() { try { object obj = (Type.GetType("HowToFish.ModConsole.ModConsoleApi, HowToFish.ModConsole", throwOnError: false)?.GetProperty("IsOpen", BindingFlags.Static | BindingFlags.Public))?.GetValue(null, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } } catch { } return false; } public IEnumerable FindSceneObjects() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) List list = new List(); for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).isLoaded) { list.AddRange(((Scene)(ref sceneAt)).GetRootGameObjects()); } } List list2 = new List(); foreach (GameObject item in list) { Transform[] componentsInChildren = item.GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { list2.Add(((Component)val).gameObject); } } return list2; } private object GetNetworkManager() { try { return _networkManagerProperty?.GetValue(null, null); } catch { return null; } } private void EnsureMoney(object player) { if (_moneyResolved) { return; } EnsureCache(); string[] names = new string[11] { "Money", "money", "_money", "Cash", "cash", "_cash", "Gold", "Coins", "Wallet", "Balance", "PlayerMoney" }; List list = new List(); if (player != null) { list.Add(player); } if (_gameInfoType != null) { list.Add(_gameInfoType); } foreach (object item in list) { Type type = ((item is Type type2) ? type2 : item.GetType()); object obj = ((item is Type) ? null : item); MemberInfo memberInfo = (MemberInfo)(((object)ReflectionUtil.FindProperty(type, names)) ?? ((object)ReflectionUtil.FindField(type, names))); if (memberInfo == null || !ReflectionUtil.TryReadInt(obj, memberInfo, out var _)) { continue; } _moneyMember = memberInfo; _moneyHost = obj; _moneyResolved = true; _log.LogInfo((object)("Money bound to " + type.Name + "." + memberInfo.Name)); break; } } private void EnsureCache() { if (Time.unscaledTime < _nextRefresh && _isCacheInitialized) { return; } _nextRefresh = Time.unscaledTime + 2f; _playerType = _playerType ?? ReflectionUtil.FindType("Player, Assembly-CSharp"); _localPlayerField = _localPlayerField ?? ReflectionUtil.FindField(_playerType, "LocalPlayer"); _blockInputsProperty = _blockInputsProperty ?? ReflectionUtil.FindProperty(_playerType, "BlockInputs"); _holdingProperty = _holdingProperty ?? ReflectionUtil.FindProperty(_playerType, "Holding"); _screenShakeProperty = _screenShakeProperty ?? ReflectionUtil.FindProperty(_playerType, "ScreenShake"); Type type = ReflectionUtil.FindType("PlayerHolding, Assembly-CSharp"); _heldItemProperty = _heldItemProperty ?? ReflectionUtil.FindProperty(type, "HeldItem"); Type type2 = ReflectionUtil.FindType("PlayerScreenShake, Assembly-CSharp"); _shakeMethod = _shakeMethod ?? type2?.GetMethod("Shake", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _gameInfoType = _gameInfoType ?? ReflectionUtil.FindType("GameInfo, Assembly-CSharp"); _waterManagerType = _waterManagerType ?? ReflectionUtil.FindType("WaterManager, Assembly-CSharp"); _isUnderWaterMethod = _isUnderWaterMethod ?? _waterManagerType?.GetMethod("IsUnderWater", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Vector3) }, null); _instanceFinderType = _instanceFinderType ?? ReflectionUtil.FindType("FishNet.InstanceFinder, FishNet.Runtime"); _networkManagerProperty = _networkManagerProperty ?? _instanceFinderType?.GetProperty("NetworkManager", BindingFlags.Static | BindingFlags.Public); _networkObjectType = _networkObjectType ?? ReflectionUtil.FindType("FishNet.Object.NetworkObject, FishNet.Runtime"); _isOwnerProperty = _isOwnerProperty ?? _networkObjectType?.GetProperty("IsOwner", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _isServerInitializedProperty = _isServerInitializedProperty ?? _networkObjectType?.GetProperty("IsServerInitialized", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _chatManagerType = _chatManagerType ?? ReflectionUtil.FindType("ChatManager, Assembly-CSharp"); _chatInstanceField = _chatInstanceField ?? ReflectionUtil.FindField(_chatManagerType, "_instance", "Instance", "instance"); if (_chatManagerType != null && _chatSendMethod == null) { string[] array = new string[6] { "SendMessage", "AddMessage", "PostMessage", "Write", "Say", "LocalMessage" }; foreach (string name in array) { MethodInfo methodInfo = ReflectionUtil.FindMethod(_chatManagerType, name, 1); if (methodInfo != null) { _chatSendMethod = methodInfo; break; } } } _isCacheInitialized = _playerType != null && _chatManagerType != null; } private void CacheNetwork(Type type) { _isServerStartedProperty = _isServerStartedProperty ?? type.GetProperty("IsServerStarted", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _isClientStartedProperty = _isClientStartedProperty ?? type.GetProperty("IsClientStarted", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } private static bool ReadBool(PropertyInfo property, object instance) { try { bool flag = default(bool); int num; if (property != null) { object value = property.GetValue(instance, null); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } } public sealed class ItemFinder { private static readonly string[] ItemHints = new string[5] { "Pickup", "InteractableItem", "DroppedItem", "InventoryItem", "HoldableItem" }; private static readonly string[] FishHints = new string[6] { "Fish", "Shark", "Crab", "Whale", "Leech", "Piranha" }; private static readonly string[] BaitHints = new string[3] { "Bait", "Lure", "Hook" }; private static readonly string[] BoatHints = new string[4] { "Boat", "Propeller", "Hull", "Rudder" }; private static readonly string[] WeaponHints = new string[8] { "Weapon", "Gun", "Rifle", "Pistol", "Shotgun", "Sniper", "Knife", "Melee" }; private static readonly string[] ToolHints = new string[9] { "Tool", "Hammer", "Axe", "Rod", "FishingRod", "FishingRodCrab", "Rod", "Pole", "FishingPole" }; private static readonly string[] RodHints = new string[9] { "Rod", "FishingRod", "Fishing Rod", "Fishing Rod(Clone)", "Crab Fishing Rod", "Crab Fishing Rod(Clone)", "FishRod", "Pole", "FishingPole" }; private static readonly string[] DurabilityNames = new string[4] { "Durability", "Condition", "CurrentDurability", "_durability" }; private static readonly string[] MaxDurabilityNames = new string[3] { "MaxDurability", "MaxHealth", "_maxDurability" }; private readonly ManualLogSource _log; private readonly GameBridge _bridge; public ItemFinder(ManualLogSource log, GameBridge bridge) { _log = log; _bridge = bridge; } public int CountCandidates(Vector3 origin) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return Collect(origin, _bridge.GetLocalPlayer()).Count; } public List Find(Vector3 origin, object player) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return Collect(origin, player); } private List Collect(Vector3 origin, object player) { //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) List list = new List(); HashSet seenRootIds = new HashSet(); object obj = (RetrieverConfig.IgnoreHeldItem.Value ? _bridge.GetHeldItem(player) : null); object obj2; if (obj == null) { obj2 = null; } else { obj2 = ((obj is Item) ? obj : null); if (obj2 == null) { object obj3 = ((obj is Component) ? obj : null); obj2 = ((obj3 != null) ? ((Component)obj3).GetComponentInParent() : null); } } Item val = (Item)obj2; GameObject val2 = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : null); int heldId = (((Object)(object)val2 != (Object)null) ? GetObjectId(val2) : 0); float num = Mathf.Max(0f, RetrieverConfig.MinRange.Value); float value = RetrieverConfig.MaxRange.Value; int num2 = Mathf.Max(1, RetrieverConfig.MaxItemsPerUse.Value); int num3 = 0; int num4 = 0; if ((Object)(object)ItemManager.Instance != (Object)null && ItemManager.Items != null) { foreach (KeyValuePair item in ItemManager.Items) { Item value2 = item.Value; if ((Object)(object)value2 == (Object)null) { continue; } num3++; if ((Object)(object)value2.Holder != (Object)null || (Object)(object)value2.SyncedHolder != (Object)null || (Object)(object)value2.BirdHolder != (Object)null) { continue; } GameObject gameObjectFromItem = GetGameObjectFromItem(value2); if (!((Object)(object)gameObjectFromItem == (Object)null)) { GameObject itemRoot = GetItemRoot(gameObjectFromItem); if (!((Object)(object)itemRoot == (Object)null)) { ProcessCandidate(itemRoot, origin, heldId, val2, num, value, seenRootIds, list); } } } } if (ItemManager._itemsUnderwater != null) { try { foreach (Item item2 in ItemManager._itemsUnderwater) { if ((Object)(object)item2 == (Object)null) { continue; } num4++; if ((Object)(object)item2.Holder != (Object)null || (Object)(object)item2.SyncedHolder != (Object)null || (Object)(object)item2.BirdHolder != (Object)null) { continue; } GameObject gameObjectFromItem2 = GetGameObjectFromItem(item2); if (!((Object)(object)gameObjectFromItem2 == (Object)null)) { GameObject itemRoot2 = GetItemRoot(gameObjectFromItem2); if (!((Object)(object)itemRoot2 == (Object)null)) { ProcessCandidate(itemRoot2, origin, heldId, val2, num, value, seenRootIds, list); } } } } catch (Exception ex) { _log.LogDebug((object)("Underwater scan failed: " + ex.Message)); } } if (list.Count == 0) { _log.LogInfo((object)($"No candidates. Scanned Items={num3}, Underwater={num4}, " + $"minRange={num:F1}, maxRange={value:F1}, origin={origin}")); } list.Sort((LostItem a, LostItem b) => a.Distance.CompareTo(b.Distance)); if (list.Count > num2) { list.RemoveRange(num2, list.Count - num2); } return list; } private void ProcessCandidate(GameObject rootObject, Vector3 origin, int heldId, GameObject heldRoot, float minRange, float maxRange, HashSet seenRootIds, List results) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: 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_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) int objectId = GetObjectId(rootObject); if (!seenRootIds.Add(objectId) || (heldId != 0 && (objectId == heldId || IsChildOf(rootObject, heldRoot))) || !LooksLikeRetrievable(rootObject, out var category)) { return; } Transform transform = rootObject.transform; Vector3 position = transform.position; if (position.y < RetrieverConfig.MinWorldY.Value || position.y > RetrieverConfig.MaxWorldY.Value) { return; } float num = Vector3.Distance(origin, position); if (num < minRange || (maxRange > 0.01f && num > maxRange)) { return; } bool underWater = _bridge.IsUnderWater(position); bool flag = _bridge.IsLikelyLava(position); if (!PassesLocation(underWater, flag) || (RetrieverConfig.RequireLineOfSight.Value && !_bridge.HasLineOfSight(origin + Vector3.up, position))) { return; } bool flag2 = _bridge.IsNetworkObjectOwner(rootObject); if (!RetrieverConfig.RetrieveOnlyOwnedItems.Value || flag2) { LostItem lostItem = new LostItem { Source = rootObject, GameObject = rootObject, Transform = transform, Rigidbody = (rootObject.GetComponent() ?? rootObject.GetComponentInParent()), Name = ((Object)rootObject).name, Category = category, Distance = num, UnderWater = underWater, LikelyLava = flag, Owned = flag2 }; FillDurability(lostItem); if (!lostItem.HasDurability || !(lostItem.Durability <= 0.01f) || !RetrieverConfig.SkipBrokenItems.Value) { results.Add(lostItem); } } } private static int GetObjectId(GameObject obj) { if ((Object)(object)obj == (Object)null) { return 0; } Component component = obj.GetComponent(); if ((Object)(object)component != (Object)null) { return ((Object)component).GetInstanceID(); } return ((Object)obj).GetInstanceID(); } private static GameObject GetGameObjectFromItem(Item item) { if ((Object)(object)item == (Object)null) { return null; } return ((Object)(object)item != (Object)null) ? ((Component)item).gameObject : null; } private static GameObject GetItemRoot(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return null; } Item componentInParent = gameObject.GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return ((Component)componentInParent).gameObject; } return null; } private static bool PassesLocation(bool underWater, bool lava) { if (RetrieverConfig.AllowFromAnywhere.Value) { if (underWater && !RetrieverConfig.AllowFromOcean.Value) { return false; } if (lava && !RetrieverConfig.AllowFromLava.Value) { return false; } return true; } return (underWater && RetrieverConfig.AllowFromOcean.Value) || (lava && RetrieverConfig.AllowFromLava.Value); } private static bool LooksLikeRetrievable(GameObject gameObject, out string category) { category = "Other"; if ((Object)(object)gameObject == (Object)null) { return false; } string name = ((Object)gameObject).name; string haystack = BuildTypeBlob(gameObject); List needles = ReflectionUtil.SplitList(RetrieverConfig.BlacklistNames.Value).ToList(); if (ReflectionUtil.NameMatches(name, needles) || ReflectionUtil.NameMatches(haystack, needles)) { return false; } if (RetrieverConfig.IgnoreBoatParts.Value && (ReflectionUtil.NameMatches(name, BoatHints) || ReflectionUtil.NameMatches(haystack, BoatHints))) { return false; } if (RetrieverConfig.IgnoreBait.Value && (ReflectionUtil.NameMatches(name, BaitHints) || ReflectionUtil.NameMatches(haystack, BaitHints))) { return false; } Item val = gameObject.GetComponent() ?? gameObject.GetComponentInParent(); if ((Object)(object)val == (Object)null) { return false; } if ((Object)(object)val.FishingRod != (Object)null) { category = "Rods"; return CategoryAllowed(category); } bool flag = ReflectionUtil.NameMatches(name, FishHints) || ReflectionUtil.NameMatches(haystack, FishHints); bool flag2 = ReflectionUtil.NameMatches(name, RodHints) || ReflectionUtil.NameMatches(haystack, RodHints); bool flag3 = ReflectionUtil.NameMatches(name, WeaponHints) || ReflectionUtil.NameMatches(haystack, WeaponHints); bool flag4 = ReflectionUtil.NameMatches(name, ToolHints) || ReflectionUtil.NameMatches(haystack, ToolHints); if (flag) { if (RetrieverConfig.IgnoreFish.Value) { return false; } category = "Fish"; } else if (flag3) { category = "Weapons"; } else if (flag2) { category = "Rods"; } else if (flag4) { category = "Tools"; } else { category = "Other"; } if ((Object)(object)gameObject.GetComponentInChildren() != (Object)null || (Object)(object)gameObject.GetComponentInChildren() != (Object)null || (Object)(object)gameObject.GetComponentInChildren() != (Object)null) { return false; } return CategoryAllowed(category); } private static bool CategoryAllowed(string category) { bool flag = false; List list = ReflectionUtil.SplitList(RetrieverConfig.AllowedItemTypes.Value).ToList(); for (int i = 0; i < list.Count; i++) { string text = list[i]; flag = true; if (text.Equals("All", StringComparison.OrdinalIgnoreCase) || text.Equals(category, StringComparison.OrdinalIgnoreCase)) { return true; } } return !flag; } private static string BuildTypeBlob(GameObject gameObject) { Component[] componentsInChildren = gameObject.GetComponentsInChildren(true); StringBuilder stringBuilder = new StringBuilder(((Object)gameObject).name); foreach (Component val in componentsInChildren) { if ((Object)(object)val != (Object)null) { stringBuilder.Append(' ').Append(((object)val).GetType().Name); } } return stringBuilder.ToString(); } private static bool IsChildOf(GameObject candidate, GameObject parent) { if ((Object)(object)candidate == (Object)null || (Object)(object)parent == (Object)null) { return false; } return candidate.transform.IsChildOf(parent.transform); } private static void FillDurability(LostItem item) { Item val = item.GameObject.GetComponent() ?? item.GameObject.GetComponentInParent(); if ((Object)(object)val == (Object)null) { return; } Type type = ((object)val).GetType(); MemberInfo memberInfo = (MemberInfo)(((object)ReflectionUtil.FindProperty(type, DurabilityNames)) ?? ((object)ReflectionUtil.FindField(type, DurabilityNames))); if (!(memberInfo == null) && ReflectionUtil.TryReadFloat(val, memberInfo, out var value)) { item.HasDurability = true; item.Durability = value; MemberInfo memberInfo2 = (MemberInfo)(((object)ReflectionUtil.FindProperty(type, MaxDurabilityNames)) ?? ((object)ReflectionUtil.FindField(type, MaxDurabilityNames))); if (memberInfo2 != null && ReflectionUtil.TryReadFloat(val, memberInfo2, out var value2) && value2 > 0f) { item.MaxDurability = value2; } else { item.MaxDurability = Mathf.Max(value, 100f); } } } } public sealed class NetworkSync { private readonly ManualLogSource _log; private readonly GameBridge _bridge; private Type _networkTransformType; private MethodInfo _teleportMethod; public bool IsServer => _bridge.IsServer; public bool CanRetrieve { get { if (RetrieverConfig.HostOnly.Value) { return _bridge.IsServer; } return true; } } public NetworkSync(ManualLogSource log, GameBridge bridge) { _log = log; _bridge = bridge; } public bool CanMove(GameObject gameObject) { if (!Object.op_Implicit((Object)(object)gameObject)) { return false; } if (_bridge.IsServer) { return true; } Item val = gameObject.GetComponent() ?? gameObject.GetComponentInParent(); if ((Object)(object)val != (Object)null) { return true; } return _bridge.IsNetworkObjectOwner(gameObject); } public void TeleportItem(GameObject gameObject, Vector3 targetPosition, Quaternion targetRotation) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)gameObject)) { return; } Rigidbody val = gameObject.GetComponent() ?? gameObject.GetComponentInParent(); if ((Object)(object)val != (Object)null) { if (!val.isKinematic) { try { val.linearVelocity = Vector3.zero; val.angularVelocity = Vector3.zero; } catch { val.velocity = Vector3.zero; val.angularVelocity = Vector3.zero; } } val.position = targetPosition; val.rotation = targetRotation; val.WakeUp(); } Transform transform = gameObject.transform; transform.position = targetPosition; transform.rotation = targetRotation; SyncNetworkTransform(gameObject, targetPosition, targetRotation); TryStartSimulateLocal(gameObject, targetPosition, targetRotation); } private void TryStartSimulateLocal(GameObject gameObject, Vector3 pos, Quaternion rot) { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) try { Item val = gameObject.GetComponent() ?? gameObject.GetComponentInParent(); if ((Object)(object)val == (Object)null) { return; } MemberInfo memberInfo = (MemberInfo)(((object)((object)val).GetType().GetProperty("RigidbodySync", BindingFlags.Instance | BindingFlags.Public)) ?? ((object)((object)val).GetType().GetField("RigidbodySync", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))); if (memberInfo == null) { return; } object obj = ((memberInfo is PropertyInfo propertyInfo) ? propertyInfo.GetValue(val) : ((FieldInfo)memberInfo).GetValue(val)); if (obj != null) { MethodInfo method = obj.GetType().GetMethod("StartSimulateLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(Vector3), typeof(Quaternion) }, null); if (method != null) { method.Invoke(obj, new object[2] { pos, rot }); _log.LogInfo((object)("-> RigidbodySync.StartSimulateLocal called on '" + ((Object)gameObject).name + "'")); } } } catch (Exception ex) { _log.LogDebug((object)("RigidbodySync.StartSimulateLocal failed: " + ex.Message)); } } private void SyncNetworkTransform(GameObject gameObject, Vector3 position, Quaternion rotation) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) EnsureCache(); if (_networkTransformType == null) { return; } Component val = gameObject.GetComponent(_networkTransformType) ?? gameObject.GetComponentInParent(_networkTransformType); if (!Object.op_Implicit((Object)(object)val)) { return; } try { if (_teleportMethod != null) { _teleportMethod.Invoke(val, new object[2] { position, rotation }); } } catch (Exception ex) { _log.LogDebug((object)("NetworkTransform teleport failed: " + ex.Message)); } } private void EnsureCache() { if (!(_networkTransformType != null)) { _networkTransformType = ReflectionUtil.FindType("FishNet.Component.Transforming.NetworkTransform, FishNet.Runtime"); if (_networkTransformType != null) { _teleportMethod = _networkTransformType.GetMethod("Teleport", new Type[2] { typeof(Vector3), typeof(Quaternion) }); } } } } public sealed class RetrieverLogic { private readonly ManualLogSource _log; private readonly GameBridge _bridge; private readonly ItemFinder _finder; private readonly NetworkSync _network; public RetrieverLogic(ManualLogSource log, GameBridge bridge, ItemFinder finder, NetworkSync network) { _log = log; _bridge = bridge; _finder = finder; _network = network; } public RetrieveResult TryRetrieve(object player, string source) { //IL_0034: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0558: Unknown result type (might be due to invalid IL or missing references) //IL_043d: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_044c: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Unknown result type (might be due to invalid IL or missing references) //IL_0450: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0463: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_046d: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) Transform playerTransform = _bridge.GetPlayerTransform(player); if ((Object)(object)playerTransform == (Object)null) { return RetrieveResult.Failed("Player transform missing."); } Vector3 playerPosition = _bridge.GetPlayerPosition(player); _log.LogInfo((object)$"Retrieve origin (cam): {playerPosition}"); List list = _finder.Find(playerPosition, player); if (list.Count == 0) { if (RetrieverConfig.ChargeEvenIfNothingFound.Value && RetrieverConfig.MoneyCost.Value > 0) { if (!TryPay(player, RetrieverConfig.MoneyCost.Value, 0f, 0, out var charged, out var error)) { return RetrieveResult.Failed(error); } return RetrieveResult.Ok("No items found. Charged the flat fee anyway.", 0, 0, 0, charged); } return RetrieveResult.Failed("No lost items found. They may have despawned, or filters excluded them."); } list = DeduplicateRodsKeepFarthest(list); float num = 0f; for (int i = 0; i < list.Count; i++) { if (list[i].Distance > num) { num = list[i].Distance; } } int num2 = ComputeCost(num, list.Count); if (RetrieverConfig.FailIfCannotAfford.Value && num2 > 0 && _bridge.TryGetMoney(player, out var money) && money < num2) { return RetrieveResult.Failed($"Need ${num2} to retrieve {list.Count} item(s). You have ${money}."); } Vector3 val = playerPosition + _bridge.GetPlayerForward(player) * RetrieverConfig.DropInFrontDistance.Value + Vector3.up * RetrieverConfig.DropHeight.Value; int num3 = 0; int num4 = 0; int num5 = 0; HashSet hashSet = new HashSet(); for (int j = 0; j < list.Count; j++) { LostItem lostItem = list[j]; GameObject targetRootObject = GetTargetRootObject(lostItem.GameObject); if ((Object)(object)targetRootObject == (Object)null || hashSet.Contains(targetRootObject)) { num4++; continue; } _log.LogInfo((object)$"Evaluating candidate item: '{lostItem.Name}' -> Root: '{((Object)targetRootObject).name}' (Category: {lostItem.Category}, Distance: {lostItem.Distance:F1}m)"); if (IsAttachedToPlayerHierarchy(targetRootObject, playerTransform)) { _log.LogInfo((object)("-> Skipped '" + ((Object)targetRootObject).name + "' because it is attached to the local player.")); num4++; continue; } Item val2 = targetRootObject.GetComponent() ?? targetRootObject.GetComponentInParent(); if ((Object)(object)val2 != (Object)null && ((Object)(object)val2.Holder != (Object)null || (Object)(object)val2.SyncedHolder != (Object)null)) { _log.LogInfo((object)("-> Skipped '" + ((Object)targetRootObject).name + "' because it is actively held.")); num4++; continue; } if (!_network.CanMove(targetRootObject)) { _log.LogInfo((object)("-> Skipped '" + ((Object)targetRootObject).name + "' due to network authority/CanMove.")); num4++; continue; } if (!ApplyDurability(lostItem, out var broke)) { num4++; continue; } if (broke && RetrieverConfig.DestroyIfBroken.Value) { if ((Object)(object)val2 != (Object)null && (Object)(object)ItemManager.Instance != (Object)null) { ItemManager._itemsUnderwater.Remove(val2); } Object.Destroy((Object)(object)targetRootObject); num5++; hashSet.Add(targetRootObject); continue; } if ((Object)(object)val2 != (Object)null && (Object)(object)ItemManager.Instance != (Object)null) { ItemManager._itemsUnderwater.Remove(val2); } Vector2 val3 = Random.insideUnitCircle * 0.2f; Vector3 dropTarget = val + new Vector3(val3.x, 0f, val3.y); MoveOrFlyRootItem(targetRootObject, dropTarget); hashSet.Add(targetRootObject); _log.LogInfo((object)("-> Successfully initiated retrieval for root item '" + ((Object)targetRootObject).name + "'")); num3++; } if (num3 == 0 && num5 == 0) { return RetrieveResult.Failed($"Found {list.Count} candidate(s) but none could be moved."); } if (!TryPay(player, RetrieverConfig.MoneyCost.Value, num, num3, out var charged2, out var error2) && RetrieverConfig.FailIfCannotAfford.Value) { _log.LogWarning((object)("Items already moved but payment failed: " + error2)); } if (RetrieverConfig.PlaySound.Value) { PlayWhoosh(val); } string message = $"Retrieved {num3} item(s) via {source}" + ((num5 > 0) ? $", destroyed {num5}" : string.Empty) + ((num4 > 0) ? $", skipped {num4}" : string.Empty) + ((charged2 > 0) ? $", charged ${charged2}" : string.Empty) + "."; return RetrieveResult.Ok(message, num3, num4, num5, charged2); } private List DeduplicateRodsKeepFarthest(List items) { if (items == null || items.Count <= 1) { return items; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); List list = new List(); for (int i = 0; i < items.Count; i++) { LostItem lostItem = items[i]; if (!string.Equals(lostItem.Category, "Rods", StringComparison.OrdinalIgnoreCase) && (lostItem.Name == null || lostItem.Name.IndexOf("Rod", StringComparison.OrdinalIgnoreCase) < 0)) { list.Add(lostItem); continue; } string key = NormalizeItemName(lostItem.Name); if (!dictionary.TryGetValue(key, out var value) || lostItem.Distance > value.Distance) { if (value != null) { _log.LogInfo((object)$"-> Skipping nearer duplicate rod '{value.Name}' at {value.Distance:F1}m (keeping {lostItem.Distance:F1}m)"); } dictionary[key] = lostItem; } else { _log.LogInfo((object)$"-> Skipping nearer duplicate rod '{lostItem.Name}' at {lostItem.Distance:F1}m (keeping {value.Distance:F1}m)"); } } List list2 = new List(list.Count + dictionary.Count); list2.AddRange(list); foreach (KeyValuePair item in dictionary) { list2.Add(item.Value); } list2.Sort((LostItem a, LostItem b) => a.Distance.CompareTo(b.Distance)); return list2; } private static string NormalizeItemName(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } string text = name.Trim(); if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - 7).Trim(); } return text; } private static GameObject GetTargetRootObject(GameObject candidate) { if ((Object)(object)candidate == (Object)null) { return null; } Item componentInParent = candidate.GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return ((Component)((Component)componentInParent).transform).gameObject; } Rigidbody componentInParent2 = candidate.GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { return ((Component)componentInParent2).gameObject; } return ((Component)candidate.transform.root).gameObject; } private static bool IsAttachedToPlayerHierarchy(GameObject obj, Transform playerTransform) { if ((Object)(object)obj == (Object)null || (Object)(object)playerTransform == (Object)null) { return false; } return obj.transform.IsChildOf(playerTransform); } private void MoveOrFlyRootItem(GameObject rootObj, Vector3 dropTarget) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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) if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(FlyToTargetRoutine(rootObj, dropTarget, 0.55f)); return; } _network.TeleportItem(rootObj, dropTarget, rootObj.transform.rotation); ForceVisible(rootObj); LogGroundDrop(rootObj, dropTarget); } private IEnumerator FlyToTargetRoutine(GameObject itemRoot, Vector3 dropPoint, float duration) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)itemRoot == (Object)null) { yield break; } Rigidbody rb = itemRoot.GetComponent() ?? itemRoot.GetComponentInParent(); bool wasKinematic = false; if ((Object)(object)rb != (Object)null) { wasKinematic = rb.isKinematic; rb.isKinematic = true; } ForceVisible(itemRoot); Vector3 startPos = itemRoot.transform.position; float elapsed = 0f; while (elapsed < duration) { if ((Object)(object)itemRoot == (Object)null) { yield break; } elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / duration); float easeT = 1f - Mathf.Pow(1f - t, 3f); Vector3 arcOffset = Vector3.up * (Mathf.Sin(t * MathF.PI) * 1.2f); itemRoot.transform.position = Vector3.Lerp(startPos, dropPoint, easeT) + arcOffset; yield return null; } if ((Object)(object)itemRoot == (Object)null) { yield break; } if ((Object)(object)rb != (Object)null) { rb.isKinematic = wasKinematic; } Quaternion finalRot = itemRoot.transform.rotation; _network.TeleportItem(itemRoot, dropPoint, finalRot); ForceVisible(itemRoot); _log.LogInfo((object)$"-> Teleported '{((Object)itemRoot).name}' to {dropPoint}"); yield return null; if ((Object)(object)itemRoot == (Object)null) { _log.LogWarning((object)"-> Item destroyed after teleport (game cleanup?)."); yield break; } float drift = Vector3.Distance(itemRoot.transform.position, dropPoint); _log.LogInfo((object)$"-> After settle: pos={itemRoot.transform.position}, drift={drift:F1}m"); if (drift > 3f) { _log.LogWarning((object)$"-> Drift {drift:F1}m — re-applying teleport + SimulateLocal."); _network.TeleportItem(itemRoot, dropPoint, finalRot); ForceVisible(itemRoot); } LogGroundDrop(itemRoot, dropPoint); } private void LogGroundDrop(GameObject itemRoot, Vector3 dropPoint) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)itemRoot == (Object)null)) { _log.LogInfo((object)$"-> '{((Object)itemRoot).name}' is on the ground in front of you at {dropPoint}. Press E to pick it up."); } } private static void ForceVisible(GameObject root) { if ((Object)(object)root == (Object)null) { return; } if (!root.activeSelf) { root.SetActive(true); } MeshRenderer[] componentsInChildren = root.GetComponentsInChildren(true); foreach (MeshRenderer val in componentsInChildren) { if ((Object)(object)val != (Object)null) { ((Renderer)val).enabled = true; } } } private static int ComputeCost(float farthest, int count) { float num = (float)RetrieverConfig.MoneyCost.Value + RetrieverConfig.CostPerDistance.Value * farthest + (float)(RetrieverConfig.CostPerItem.Value * count); return Mathf.Max(0, Mathf.RoundToInt(num)); } private bool TryPay(object player, int flat, float farthest, int count, out int charged, out string error) { int amount = ComputeCost(farthest, count); if (flat <= 0 && RetrieverConfig.CostPerDistance.Value <= 0f && RetrieverConfig.CostPerItem.Value <= 0) { amount = 0; } return _bridge.TryCharge(player, amount, out charged, out error); } private static bool ApplyDurability(LostItem item, out bool broke) { broke = false; if (!item.HasDurability) { return true; } float num = Mathf.Clamp(RetrieverConfig.DurabilityLossPercent.Value, 0f, 100f); float num2 = item.Durability; if (num > 0f) { num2 -= item.Durability * (num / 100f); } num2 -= Mathf.Max(0f, RetrieverConfig.DurabilityLossFlat.Value); num2 = Mathf.Max(0f, num2); broke = num2 <= 0.01f; item.Durability = num2; WriteDurability(item, num2); return true; } private static void WriteDurability(LostItem item, float value) { string[] names = new string[5] { "Durability", "Condition", "Health", "CurrentDurability", "_durability" }; Component[] components = item.GameObject.GetComponents(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null)) { MemberInfo memberInfo = (MemberInfo)(((object)ReflectionUtil.FindProperty(((object)val).GetType(), names)) ?? ((object)ReflectionUtil.FindField(((object)val).GetType(), names))); if (memberInfo != null && ReflectionUtil.SetMember(val, memberInfo, value)) { break; } } } } private static void PlayWhoosh(Vector3 position) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) try { AudioClip val = AudioClip.Create("retriever-whoosh", 8000, 1, 8000, false); float[] array = new float[8000]; for (int i = 0; i < array.Length; i++) { float num = (float)i / 8000f; array[i] = Mathf.Sin(MathF.PI * 2f * Mathf.Lerp(900f, 180f, num) * num) * (1f - num) * 0.2f; } val.SetData(array, 0); AudioSource.PlayClipAtPoint(val, position, 0.6f); } catch { } } } } namespace HowToFish.ItemRetriever.Models { public sealed class LostItem { public object Source { get; set; } public GameObject GameObject { get; set; } public Transform Transform { get; set; } public Rigidbody Rigidbody { get; set; } public string Name { get; set; } public string Category { get; set; } public float Distance { get; set; } public bool UnderWater { get; set; } public bool LikelyLava { get; set; } public bool Owned { get; set; } public float Durability { get; set; } public float MaxDurability { get; set; } public bool HasDurability { get; set; } public GameObject RootGameObject => ((Object)(object)Rigidbody != (Object)null) ? ((Component)Rigidbody).gameObject : (Object.op_Implicit((Object)(object)GameObject) ? ((Component)GameObject.transform.root).gameObject : null); } public sealed class RetrieveResult { public bool Success { get; private set; } public string Message { get; private set; } public int Moved { get; private set; } public int Skipped { get; private set; } public int Destroyed { get; private set; } public int Charged { get; private set; } public int TotalProcessed => Moved + Skipped + Destroyed; public static RetrieveResult Failed(string message) { return new RetrieveResult { Success = false, Message = (message ?? string.Empty), Moved = 0, Skipped = 0, Destroyed = 0, Charged = 0 }; } public static RetrieveResult Ok(string message, int moved, int skipped, int destroyed, int charged) { return new RetrieveResult { Success = true, Message = (message ?? string.Empty), Moved = moved, Skipped = skipped, Destroyed = destroyed, Charged = charged }; } } } namespace HowToFish.ItemRetriever.Config { public static class RetrieverConfig { public static ConfigEntry Enabled { get; private set; } public static ConfigEntry RetrieveKey { get; private set; } public static ConfigEntry IgnoreWhenChatOpen { get; private set; } public static ConfigEntry MaxRange { get; private set; } public static ConfigEntry MinRange { get; private set; } public static ConfigEntry MaxItemsPerUse { get; private set; } public static ConfigEntry DropInFrontDistance { get; private set; } public static ConfigEntry DropHeight { get; private set; } public static ConfigEntry CooldownSeconds { get; private set; } public static ConfigEntry MoneyCost { get; private set; } public static ConfigEntry CostPerDistance { get; private set; } public static ConfigEntry CostPerItem { get; private set; } public static ConfigEntry FailIfCannotAfford { get; private set; } public static ConfigEntry ChargeEvenIfNothingFound { get; private set; } public static ConfigEntry DurabilityLossPercent { get; private set; } public static ConfigEntry DurabilityLossFlat { get; private set; } public static ConfigEntry SkipBrokenItems { get; private set; } public static ConfigEntry DestroyIfBroken { get; private set; } public static ConfigEntry HostOnly { get; private set; } public static ConfigEntry RetrieveOnlyOwnedItems { get; private set; } public static ConfigEntry RequireLineOfSight { get; private set; } public static ConfigEntry AllowFromOcean { get; private set; } public static ConfigEntry AllowFromLava { get; private set; } public static ConfigEntry AllowFromAnywhere { get; private set; } public static ConfigEntry MinWorldY { get; private set; } public static ConfigEntry MaxWorldY { get; private set; } public static ConfigEntry AllowedItemTypes { get; private set; } public static ConfigEntry BlacklistNames { get; private set; } public static ConfigEntry IgnoreFish { get; private set; } public static ConfigEntry IgnoreBait { get; private set; } public static ConfigEntry IgnoreBoatParts { get; private set; } public static ConfigEntry IgnoreHeldItem { get; private set; } public static ConfigEntry PlaySound { get; private set; } public static ConfigEntry ShowChatMessage { get; private set; } public static ConfigEntry ScreenShake { get; private set; } public static ConfigEntry LogToConsole { get; private set; } public static ConfigEntry ZeroVelocityOnRetrieve { get; private set; } public static void Bind(ConfigFile config) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Master switch for Item Retriever."); RetrieveKey = config.Bind("Controls", "RetrieveKey", (KeyCode)289, "Press to pull lost items back in front of you."); IgnoreWhenChatOpen = config.Bind("Controls", "IgnoreWhenChatOpen", true, "Ignore the hotkey while chat, console, or BlockInputs is active."); MaxRange = config.Bind("Range", "MaxRange", 8f, "Maximum distance in meters. 0 = unlimited."); MinRange = config.Bind("Range", "MinRange", 0f, "Ignore items already this close. Stops you scooping the pile at your feet."); MaxItemsPerUse = config.Bind("Range", "MaxItemsPerUse", 25, new ConfigDescription("Hard cap per press.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 200), Array.Empty())); DropInFrontDistance = config.Bind("Range", "DropInFrontDistance", 1.6f, "How far in front of the player retrieved items appear."); DropHeight = config.Bind("Range", "DropHeight", 0.85f, "Extra height so items do not spawn inside the ground."); CooldownSeconds = config.Bind("Range", "CooldownSeconds", 8f, "Seconds before you can retrieve again. 0 = no cooldown."); MoneyCost = config.Bind("Cost", "MoneyCost", 0, "Flat money charged per retrieve, before distance/item extras. 0 = free."); CostPerDistance = config.Bind("Cost", "CostPerDistance", 0f, "Extra money per meter of the farthest retrieved item."); CostPerItem = config.Bind("Cost", "CostPerItem", 0, "Extra money charged per item actually moved."); FailIfCannotAfford = config.Bind("Cost", "FailIfCannotAfford", true, "If true, do nothing when you cannot pay the full cost."); ChargeEvenIfNothingFound = config.Bind("Cost", "ChargeEvenIfNothingFound", false, "Charge the flat fee even when no items were found."); DurabilityLossPercent = config.Bind("Durability", "DurabilityLossPercent", 0f, new ConfigDescription("Percent of current durability removed on retrieve (0-100).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); DurabilityLossFlat = config.Bind("Durability", "DurabilityLossFlat", 0f, "Flat durability removed after the percent cut."); SkipBrokenItems = config.Bind("Durability", "SkipBrokenItems", true, "Do not retrieve items that already have 0 durability."); DestroyIfBroken = config.Bind("Durability", "DestroyIfBroken", false, "If durability hits 0 during retrieve, destroy the item instead of returning it."); HostOnly = config.Bind("Multiplayer", "HostOnly", true, "Only the lobby host may retrieve. Safest with FishNet authority."); RetrieveOnlyOwnedItems = config.Bind("Multiplayer", "RetrieveOnlyOwnedItems", false, "Only move items this player owns / originally dropped."); RequireLineOfSight = config.Bind("Restrictions", "RequireLineOfSight", false, "Skip items with terrain between you and them."); AllowFromOcean = config.Bind("Restrictions", "AllowFromOcean", true, "Retrieve items that are underwater."); AllowFromLava = config.Bind("Restrictions", "AllowFromLava", true, "Retrieve items that sit very low / in lava-like volumes."); AllowFromAnywhere = config.Bind("Restrictions", "AllowFromAnywhere", true, "If true, land items far away are also retrieved. If false, only ocean/lava."); MinWorldY = config.Bind("Restrictions", "MinWorldY", -500f, "Ignore items below this world Y."); MaxWorldY = config.Bind("Restrictions", "MaxWorldY", 500f, "Ignore items above this world Y."); AllowedItemTypes = config.Bind("Filters", "AllowedItemTypes", "All", "Comma list: All, Weapons, Tools, Rods, Fish, Other. Case-insensitive."); BlacklistNames = config.Bind("Filters", "BlacklistNames", "Bait,Boat,Player,Camera,Water,Seagull,Bird,Clam,Rock,Prop,Terrain", "Comma list of name fragments to skip."); IgnoreFish = config.Bind("Filters", "IgnoreFish", true, "Skip living / flopping fish so you do not vacuum the whole ocean."); IgnoreBait = config.Bind("Filters", "IgnoreBait", true, "Skip fishing bait objects."); IgnoreBoatParts = config.Bind("Filters", "IgnoreBoatParts", true, "Skip the boat and attached parts."); IgnoreHeldItem = config.Bind("Filters", "IgnoreHeldItem", true, "Never teleport the item currently in your hands."); PlaySound = config.Bind("Feedback", "PlaySound", true, "Play a short whoosh when something is retrieved."); ShowChatMessage = config.Bind("Feedback", "ShowChatMessage", true, "Print a local chat / hint line with the result."); ScreenShake = config.Bind("Feedback", "ScreenShake", false, "Tiny camera shake on a successful retrieve."); LogToConsole = config.Bind("Feedback", "LogToConsole", true, "Write results to the BepInEx log."); ZeroVelocityOnRetrieve = config.Bind("Feedback", "ZeroVelocityOnRetrieve", true, "Kill rigidbody velocity so items do not immediately yeet again."); } } }