Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Smartpickup v1.0.7
BepInEx\plugins\SmartPickup.dll
Decompiled 3 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SmartPickup")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SmartPickup")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("1c4fa49b-2311-4587-a0a0-1095f2b4d1c3")] [assembly: AssemblyFileVersion("1.0.7.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.7.0")] namespace SmartAutopickupMod; internal static class PluginInfo { internal const string Guid = "com.b0n3.smartpickup"; internal const string LegacyGuid = "com.yourname.valheim.smartautopickup"; internal const string Name = "SmartPickup"; internal const string Version = "1.0.7"; internal const string ModeMessagePrefix = "Auto-Pickup:"; } public enum AutopickupMode { Normal, Smart, Off } [BepInPlugin("com.b0n3.smartpickup", "SmartPickup", "1.0.7")] public class SmartAutopickup : BaseUnityPlugin { private const float MinPickupRange = 0.5f; private const float MaxPickupRange = 100f; private static readonly FieldInfo VanillaAutopickupEnabledField = AccessTools.Field(typeof(Player), "m_enableAutoPickup"); private static readonly FieldInfo VanillaAutopickupRangeField = AccessTools.Field(typeof(Player), "m_autoPickupRange"); private static Player trackedUpdatePlayer; private static bool trackedVanillaAutopickupState; private static bool hasWarnedMissingVanillaAutopickupField; private static bool hasWarnedMissingVanillaAutopickupRangeField; private static Player trackedRangePlayer; private static float trackedOriginalVanillaAutopickupRange; private static bool hasTrackedOriginalVanillaAutopickupRange; public static AutopickupMode CurrentMode; public static ManualLogSource Log; public static ConfigEntry<AutopickupMode> DefaultMode; public static ConfigEntry<int> PickupRange; public static ConfigEntry<bool> ApplyCustomRangeInNormalMode; private const string LegacyMigrationMarkerFileName = "com.b0n3.smartpickup.migrated"; private void Awake() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; NormalizePickupRangeConfigIfNeeded(); DefaultMode = ((BaseUnityPlugin)this).Config.Bind<AutopickupMode>("General", "DefaultMode", AutopickupMode.Normal, new ConfigDescription("Default autopickup mode at startup. Changing it at runtime also applies it immediately.", (AcceptableValueBase)null, Array.Empty<object>())); PickupRange = ((BaseUnityPlugin)this).Config.Bind<int>("General", "PickupRange", 5, "Pickup range for smart mode in whole game units. Valid values are 1 through 100."); ApplyCustomRangeInNormalMode = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ApplyCustomRangeInNormalMode", false, "When enabled, normal mode uses SmartPickup's configurable pull range instead of Valheim's default autopickup range."); TryImportLegacyConfigValues(); DefaultMode.SettingChanged += OnDefaultModeChanged; PickupRange.SettingChanged += OnPickupRangeChanged; ApplyCustomRangeInNormalMode.SettingChanged += OnApplyCustomRangeInNormalModeChanged; PickupRange.Value = Mathf.RoundToInt(GetConfiguredPickupRange()); CurrentMode = DefaultMode.Value; ApplyVanillaAutopickupState(); try { new Harmony("com.b0n3.smartpickup").PatchAll(); Log.LogInfo((object)$"Smart Autopickup mod loaded. Mode: {CurrentMode}, Pickup Range: {PickupRange.Value}, Custom Range In Normal Mode: {ApplyCustomRangeInNormalMode.Value}"); } catch (Exception ex) { Log.LogError((object)("Error in Awake: " + ex)); } } private void TryImportLegacyConfigValues() { string text = Path.Combine(Paths.ConfigPath, "com.yourname.valheim.smartautopickup.cfg"); if (!HasCompletedLegacyMigration() && File.Exists(text)) { string legacyConfigValue = GetLegacyConfigValue(text, "DefaultMode"); if (!string.IsNullOrEmpty(legacyConfigValue) && Enum.TryParse<AutopickupMode>(legacyConfigValue, ignoreCase: true, out var result)) { DefaultMode.Value = result; } string legacyConfigValue2 = GetLegacyConfigValue(text, "PickupRange"); if (!string.IsNullOrEmpty(legacyConfigValue2) && (float.TryParse(legacyConfigValue2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || float.TryParse(legacyConfigValue2, NumberStyles.Float, CultureInfo.CurrentCulture, out result2))) { PickupRange.Value = Mathf.Clamp(Mathf.RoundToInt(result2), (int)Math.Ceiling(0.5), 100); } string legacyConfigValue3 = GetLegacyConfigValue(text, "ApplyCustomRangeInNormalMode"); if (!string.IsNullOrEmpty(legacyConfigValue3) && bool.TryParse(legacyConfigValue3, out var result3)) { ApplyCustomRangeInNormalMode.Value = result3; } MarkLegacyMigrationComplete(); Log.LogInfo((object)"Migrated SmartPickup config from 'com.yourname.valheim.smartautopickup' to 'com.b0n3.smartpickup'."); } } private static bool HasCompletedLegacyMigration() { return File.Exists(Path.Combine(Paths.ConfigPath, "com.b0n3.smartpickup.migrated")); } private static void MarkLegacyMigrationComplete() { File.WriteAllText(Path.Combine(Paths.ConfigPath, "com.b0n3.smartpickup.migrated"), "1.0.7"); } private static string GetLegacyConfigValue(string configPath, string key) { bool flag = false; foreach (string item in File.ReadLines(configPath)) { string text = item.Trim(); if (text.Length == 0 || text.StartsWith("#") || text.StartsWith("##")) { continue; } if (text.StartsWith("[") && text.EndsWith("]")) { flag = string.Equals(text, "[General]", StringComparison.OrdinalIgnoreCase); } else if (flag && text.StartsWith(key + " ", StringComparison.Ordinal)) { int num = text.IndexOf('='); if (num < 0) { return null; } return text.Substring(num + 1).Trim(); } } return null; } private static void NormalizePickupRangeConfigIfNeeded() { string path = Path.Combine(Paths.ConfigPath, "com.b0n3.smartpickup.cfg"); if (!File.Exists(path)) { return; } string[] array = File.ReadAllLines(path); bool flag = false; for (int i = 0; i < array.Length; i++) { if (!array[i].StartsWith("PickupRange =")) { continue; } string s = array[i].Substring("PickupRange =".Length).Trim(); if (!float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !float.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out result)) { return; } string text = "PickupRange = " + Mathf.Clamp(Mathf.RoundToInt(result), (int)Math.Ceiling(0.5), 100).ToString(CultureInfo.InvariantCulture); if (!string.Equals(array[i], text, StringComparison.Ordinal)) { array[i] = text; flag = true; } break; } if (flag) { File.WriteAllLines(path, array); Log.LogInfo((object)"Normalized PickupRange to a whole-number value for stepped slider behavior."); } } private void OnDefaultModeChanged(object sender, EventArgs e) { CurrentMode = DefaultMode.Value; ApplyVanillaAutopickupState(); Log.LogInfo((object)("Default autopickup mode updated. Active mode is now: " + CurrentMode)); if ((Object)(object)Player.m_localPlayer != (Object)null) { string text = ((CurrentMode == AutopickupMode.Normal) ? "On" : CurrentMode.ToString()); ((Character)Player.m_localPlayer).Message((MessageType)1, "Auto-Pickup:" + text, 0, (Sprite)null); } } private void OnPickupRangeChanged(object sender, EventArgs e) { int num = Mathf.RoundToInt(GetConfiguredPickupRange()); if (PickupRange.Value != num) { PickupRange.Value = num; return; } ApplyVanillaAutopickupState(); Log.LogInfo((object)("Smart pickup range updated to: " + PickupRange.Value)); } private void OnApplyCustomRangeInNormalModeChanged(object sender, EventArgs e) { ApplyVanillaAutopickupState(); Log.LogInfo((object)("Custom range in normal mode is now: " + ApplyCustomRangeInNormalMode.Value)); } private static AutopickupMode GetNextMode(AutopickupMode currentMode) { return currentMode switch { AutopickupMode.Normal => AutopickupMode.Smart, AutopickupMode.Smart => AutopickupMode.Off, _ => AutopickupMode.Normal, }; } internal static void CycleModeFromVanillaToggle(Player player) { CurrentMode = GetNextMode(CurrentMode); ApplyVanillaAutopickupState(); Log.LogInfo((object)("Autopickup mode switched to: " + CurrentMode)); if ((Object)(object)player != (Object)null) { string text = ((CurrentMode == AutopickupMode.Normal) ? "On" : CurrentMode.ToString()); ((Character)player).Message((MessageType)1, "Auto-Pickup:" + text, 0, (Sprite)null); } } internal static float GetConfiguredPickupRange() { return Mathf.Clamp((float)PickupRange.Value, 0.5f, 100f); } internal static void BeginTrackingVanillaAutopickupState(Player player) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { trackedUpdatePlayer = null; return; } trackedUpdatePlayer = player; trackedVanillaAutopickupState = GetVanillaAutopickupEnabled(); } internal static void EndTrackingVanillaAutopickupState(Player player) { if ((Object)(object)trackedUpdatePlayer == (Object)(object)player) { trackedUpdatePlayer = null; } } internal static bool ShouldHandleVanillaAutopickupToggleMessage(Player player) { if ((Object)(object)trackedUpdatePlayer == (Object)(object)player) { return GetVanillaAutopickupEnabled() != trackedVanillaAutopickupState; } return false; } internal static void ApplyVanillaAutopickupStateForLifecycle() { ApplyVanillaAutopickupState(); } private static void ApplyVanillaAutopickupState() { TrackOriginalVanillaAutopickupRange(Player.m_localPlayer); bool flag = CurrentMode != AutopickupMode.Off; if (GetVanillaAutopickupEnabled() != flag) { SetVanillaAutopickupEnabled(flag); } ApplyVanillaAutopickupRange(Player.m_localPlayer); } private static bool GetVanillaAutopickupEnabled() { if (VanillaAutopickupEnabledField == null) { WarnMissingVanillaAutopickupField(); return false; } return (bool)VanillaAutopickupEnabledField.GetValue(null); } private static void SetVanillaAutopickupEnabled(bool enabled) { if (VanillaAutopickupEnabledField == null) { WarnMissingVanillaAutopickupField(); } else { VanillaAutopickupEnabledField.SetValue(null, enabled); } } private static void WarnMissingVanillaAutopickupField() { if (!hasWarnedMissingVanillaAutopickupField && Log != null) { hasWarnedMissingVanillaAutopickupField = true; Log.LogWarning((object)"SmartPickup could not resolve Player.m_enableAutoPickup. Valheim may have changed and autopickup mode syncing may not work correctly."); } } private static void TrackOriginalVanillaAutopickupRange(Player player) { if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { trackedRangePlayer = null; hasTrackedOriginalVanillaAutopickupRange = false; } else if (!((Object)(object)trackedRangePlayer == (Object)(object)player) || !hasTrackedOriginalVanillaAutopickupRange) { trackedRangePlayer = player; if (TryGetVanillaAutopickupRange(player, out var range)) { trackedOriginalVanillaAutopickupRange = range; hasTrackedOriginalVanillaAutopickupRange = true; } else { hasTrackedOriginalVanillaAutopickupRange = false; } } } private static void ApplyVanillaAutopickupRange(Player player) { if (!((Object)(object)player == (Object)null) && TryGetVanillaAutopickupRange(player, out var range)) { float num = range; if (CurrentMode == AutopickupMode.Normal && ApplyCustomRangeInNormalMode.Value) { num = GetConfiguredPickupRange(); } else if (hasTrackedOriginalVanillaAutopickupRange) { num = trackedOriginalVanillaAutopickupRange; } if (!Mathf.Approximately(range, num)) { SetVanillaAutopickupRange(player, num); } } } private static bool TryGetVanillaAutopickupRange(Player player, out float range) { if (VanillaAutopickupRangeField == null) { WarnMissingVanillaAutopickupRangeField(); range = 0f; return false; } range = (float)VanillaAutopickupRangeField.GetValue(player); return true; } private static void SetVanillaAutopickupRange(Player player, float range) { if (VanillaAutopickupRangeField == null) { WarnMissingVanillaAutopickupRangeField(); } else { VanillaAutopickupRangeField.SetValue(player, range); } } private static void WarnMissingVanillaAutopickupRangeField() { if (!hasWarnedMissingVanillaAutopickupRangeField && Log != null) { hasWarnedMissingVanillaAutopickupRangeField = true; Log.LogWarning((object)"SmartPickup could not resolve Player.m_autoPickupRange. Custom range syncing may not work correctly."); } } } [HarmonyPatch(typeof(Player), "SetLocalPlayer")] public class Player_SetLocalPlayer_Patch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { SmartAutopickup.ApplyVanillaAutopickupStateForLifecycle(); } } } [HarmonyPatch(typeof(Player), "Update")] public class Player_Update_Patch { private static void Prefix(Player __instance) { SmartAutopickup.BeginTrackingVanillaAutopickupState(__instance); } private static void Postfix(Player __instance) { SmartAutopickup.EndTrackingVanillaAutopickupState(__instance); } } [HarmonyPatch(typeof(Player), "AutoPickup")] public class Player_AutoPickup_Patch { private const int OverlapBufferSize = 128; private const float PullSpeed = 15f; private const float DirectPickupDistance = 0.3f; private const float DirectPickupDistanceSqr = 0.09f; private static readonly Collider[] OverlapBuffer = (Collider[])(object)new Collider[128]; private static readonly HashSet<string> InventoryItemIdsBuffer = new HashSet<string>(); private static readonly FieldInfo AutoPickupMaskField = AccessTools.Field(typeof(Player), "m_autoPickupMask"); private static readonly FieldInfo ItemDropPrefabField = AccessTools.Field(typeof(ItemData), "m_dropPrefab"); private static bool hasWarnedMissingAutoPickupMaskField; private static bool Prefix(Player __instance, float dt) { try { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (SmartAutopickup.CurrentMode == AutopickupMode.Normal) { return true; } if (SmartAutopickup.CurrentMode == AutopickupMode.Off) { return false; } if (SmartAutopickup.CurrentMode == AutopickupMode.Smart) { return RunCustomAutoPickup(__instance, dt, requireExistingInventoryItem: true); } } catch (Exception ex) { SmartAutopickup.Log.LogError((object)("Error in Player_AutoPickup_Patch Prefix: " + ex)); } return true; } private static bool RunCustomAutoPickup(Player player, float dt, bool requireExistingInventoryItem) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_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_004e: 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_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014f: 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_0190: 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_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: 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_01d9: Unknown result type (might be due to invalid IL or missing references) if (((Character)player).IsTeleporting()) { return false; } Vector3 val = ((Component)player).transform.position + Vector3.up; int autoPickupMask = GetAutoPickupMask(player); Inventory inventory = ((Humanoid)player).GetInventory(); float configuredPickupRange = SmartAutopickup.GetConfiguredPickupRange(); float num = configuredPickupRange * configuredPickupRange; HashSet<string> hashSet = (requireExistingInventoryItem ? GetInventoryItemIds(inventory) : null); Collider[] array = OverlapBuffer; int num2 = Physics.OverlapSphereNonAlloc(val, configuredPickupRange, OverlapBuffer, autoPickupMask); if (num2 >= OverlapBuffer.Length) { array = Physics.OverlapSphere(val, configuredPickupRange, autoPickupMask); num2 = array.Length; } for (int i = 0; i < num2; i++) { Collider val2 = array[i]; if ((Object)(object)val2 == (Object)null || (Object)(object)val2.attachedRigidbody == (Object)null || !TryGetItemDrop(val2, out var itemDrop, out var floatingTerrainDummy) || !CanCustomPickupItem(player, itemDrop)) { continue; } string itemIdentifier = GetItemIdentifier(itemDrop.m_itemData); if ((requireExistingInventoryItem && (string.IsNullOrEmpty(itemIdentifier) || hashSet == null || !hashSet.Contains(itemIdentifier))) || !itemDrop.CanPickup(true) || itemDrop.InTar()) { continue; } itemDrop.Load(); if (!inventory.CanAddItem(itemDrop.m_itemData, -1) || inventory.GetTotalWeight() + itemDrop.m_itemData.GetWeight(-1) > player.GetMaxCarryWeight()) { continue; } Vector3 val3 = val - ((Component)itemDrop).transform.position; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude > num) { continue; } if (sqrMagnitude < 0.09f) { ((Humanoid)player).Pickup(((Component)itemDrop).gameObject, true, true); } else if (CanAttractItem(itemDrop)) { Vector3 val4 = ((Vector3)(ref val3)).normalized * 15f * dt; Transform transform = ((Component)itemDrop).transform; transform.position += val4; if ((Object)(object)floatingTerrainDummy != (Object)null) { Transform transform2 = ((Component)floatingTerrainDummy).transform; transform2.position += val4; } } } return false; } private static HashSet<string> GetInventoryItemIds(Inventory inventory) { InventoryItemIdsBuffer.Clear(); foreach (ItemData allItem in inventory.GetAllItems()) { string itemIdentifier = GetItemIdentifier(allItem); if (!string.IsNullOrEmpty(itemIdentifier)) { InventoryItemIdsBuffer.Add(itemIdentifier); } } return InventoryItemIdsBuffer; } private static int GetAutoPickupMask(Player player) { if (AutoPickupMaskField == null) { WarnMissingAutoPickupMaskField(); return 0; } return (int)AutoPickupMaskField.GetValue(player); } private static string GetItemIdentifier(ItemData itemData) { object? obj = ItemDropPrefabField?.GetValue(itemData); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((Object)val).name)) { return ((Object)val).name; } return GetItemName(itemData); } private static string GetItemName(ItemData itemData) { return itemData?.m_shared?.m_name; } private static bool CanCustomPickupItem(Player player, ItemDrop itemDrop) { if ((Object)(object)itemDrop == (Object)null || !itemDrop.m_autoPickup || itemDrop.IsPiece()) { return false; } string itemName = GetItemName(itemDrop.m_itemData); if (string.IsNullOrEmpty(itemName) || ((Humanoid)player).HaveUniqueKey(itemName)) { return false; } ZNetView component = ((Component)itemDrop).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { return component.IsValid(); } return false; } private static bool TryGetItemDrop(Collider collider, out ItemDrop itemDrop, out FloatingTerrainDummy floatingTerrainDummy) { itemDrop = null; floatingTerrainDummy = null; Rigidbody attachedRigidbody = collider.attachedRigidbody; if ((Object)(object)attachedRigidbody == (Object)null) { return false; } itemDrop = ((Component)attachedRigidbody).GetComponent<ItemDrop>(); if ((Object)(object)itemDrop != (Object)null) { return true; } floatingTerrainDummy = ((Component)attachedRigidbody).GetComponent<FloatingTerrainDummy>(); if ((Object)(object)floatingTerrainDummy != (Object)null && (Object)(object)floatingTerrainDummy.m_parent != (Object)null) { itemDrop = ((Component)floatingTerrainDummy.m_parent).gameObject.GetComponent<ItemDrop>(); } return (Object)(object)itemDrop != (Object)null; } private static bool CanAttractItem(ItemDrop itemDrop) { ZNetView component = ((Component)itemDrop).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { return component.IsOwner(); } return false; } private static void WarnMissingAutoPickupMaskField() { if (!hasWarnedMissingAutoPickupMaskField && SmartAutopickup.Log != null) { hasWarnedMissingAutoPickupMaskField = true; SmartAutopickup.Log.LogWarning((object)"SmartPickup could not resolve Player.m_autoPickupMask. Custom pickup scanning may not work correctly on this Valheim version."); } } } [HarmonyPatch(typeof(Player), "Message")] public class Player_Message_Patch { private static bool Prefix(Player __instance, MessageType type, string msg, int amount, Sprite icon) { try { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (msg != null && msg.StartsWith("$hud_autopickup:")) { if (!SmartAutopickup.ShouldHandleVanillaAutopickupToggleMessage(__instance)) { return true; } SmartAutopickup.CycleModeFromVanillaToggle(__instance); return false; } } catch (Exception ex) { SmartAutopickup.Log.LogError((object)("Error in Player_Message_Patch Prefix: " + ex)); } return true; } }