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 Gatherer v1.3.1
ValheimGatherer.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; using UnityEngine.UI; using ValheimGatherer.Configuration; using ValheimGatherer.Core; using ValheimGatherer.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ValheimGatherer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.1.0")] [assembly: AssemblyInformationalVersion("1.3.1+5d9bb984594ad6806304be2346cd041a768f0cf9")] [assembly: AssemblyProduct("ValheimGatherer")] [assembly: AssemblyTitle("ValheimGatherer")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ValheimGatherer { [BepInPlugin("com.watchdoge.valheim.gatherer", "Gatherer", "1.3.1")] public class GathererPlugin : BaseUnityPlugin { public const string PluginGUID = "com.watchdoge.valheim.gatherer"; public const string PluginName = "Gatherer"; public const string PluginVersion = "1.3.1"; private Harmony? _harmony; public static GathererConfig? ModConfig { get; private set; } private void Awake() { ModConfig = new GathererConfig(((BaseUnityPlugin)this).Config); GathererManager.Instance.Initialize(ModConfig, ((BaseUnityPlugin)this).Logger); ((Component)this).gameObject.AddComponent<ProximityScanner>(); GathererInGameUI.Attach(((Component)this).gameObject); _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "com.watchdoge.valheim.gatherer"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Gatherer v1.3.1 loaded successfully."); } private void OnDestroy() { ResourcePinPresentation.Reset(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace ValheimGatherer.UI { public class GathererInGameUI : MonoBehaviour { private static GathererInGameUI? _instance; private bool _isOpen; private Rect _windowRect = new Rect(80f, 80f, 720f, 520f); private Vector2 _scrollPos = Vector2.zero; private int _activeTab; private GUIStyle? _winStyle; private GUIStyle? _headerStyle; private GUIStyle? _tabStyle; private GUIStyle? _tabActiveStyle; private GUIStyle? _sectionStyle; private GUIStyle? _toggleStyle; private GUIStyle? _labelStyle; private GUIStyle? _btnStyle; private Texture2D? _bgTexture; private Texture2D? _tabActiveTexture; public static GathererInGameUI? Instance => _instance; public static bool IsMenuOpen { get { if ((Object)(object)_instance != (Object)null) { return _instance._isOpen; } return false; } } public static void Attach(GameObject root) { if ((Object)(object)_instance == (Object)null) { _instance = root.AddComponent<GathererInGameUI>(); } } private void Awake() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) _instance = this; _windowRect = new Rect((float)Mathf.Max(20, (Screen.width - 720) / 2), (float)Mathf.Max(20, (Screen.height - 520) / 2), 720f, 520f); } private void Update() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) GathererConfig config = GathererManager.Instance.Config; if (config != null) { if (Input.GetKeyDown(config.ToggleUiKey.Value)) { ToggleMenu(); } if (_isOpen && Input.GetKeyDown((KeyCode)27)) { _isOpen = false; TakeInputFocus(focus: false); } if (_isOpen) { ZCursor.LockState = (CursorLockMode)0; ZCursor.Show(); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } } public void ToggleMenu() { _isOpen = !_isOpen; TakeInputFocus(_isOpen); } private void TakeInputFocus(bool focus) { if (focus) { ZCursor.LockState = (CursorLockMode)0; ZCursor.Show(); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } else if ((Object)(object)Player.m_localPlayer != (Object)null && !Menu.IsVisible() && !InventoryGui.IsVisible() && !Minimap.IsOpen()) { ZCursor.LockState = (CursorLockMode)1; ZCursor.Hide(); Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } } private void InitStyles() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Expected O, but got Unknown //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Expected O, but got Unknown //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Expected O, but got Unknown //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Expected O, but got Unknown //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Expected O, but got Unknown if (_winStyle == null) { _bgTexture = MakeSolidTexture(1, 1, new Color(0.12f, 0.14f, 0.17f, 0.96f)); _tabActiveTexture = MakeSolidTexture(1, 1, new Color(0.24f, 0.38f, 0.52f, 1f)); _winStyle = new GUIStyle(GUI.skin.window); _winStyle.normal.background = _bgTexture; _winStyle.normal.textColor = new Color(0.95f, 0.85f, 0.55f); _winStyle.fontSize = 15; _winStyle.fontStyle = (FontStyle)1; _winStyle.alignment = (TextAnchor)1; _winStyle.padding = new RectOffset(14, 14, 28, 14); _headerStyle = new GUIStyle(GUI.skin.label); _headerStyle.fontSize = 13; _headerStyle.fontStyle = (FontStyle)1; _headerStyle.normal.textColor = new Color(0.95f, 0.85f, 0.55f); _headerStyle.margin = new RectOffset(0, 0, 10, 4); _sectionStyle = new GUIStyle(GUI.skin.label); _sectionStyle.fontSize = 12; _sectionStyle.fontStyle = (FontStyle)1; _sectionStyle.normal.textColor = new Color(0.55f, 0.85f, 0.95f); _sectionStyle.margin = new RectOffset(0, 0, 8, 2); _tabStyle = new GUIStyle(GUI.skin.button); _tabStyle.fontSize = 12; _tabStyle.normal.textColor = Color.white; _tabStyle.fixedHeight = 28f; _tabActiveStyle = new GUIStyle(_tabStyle); _tabActiveStyle.normal.background = _tabActiveTexture; _tabActiveStyle.fontStyle = (FontStyle)1; _tabActiveStyle.normal.textColor = Color.yellow; _toggleStyle = new GUIStyle(GUI.skin.toggle); _toggleStyle.fontSize = 12; _toggleStyle.normal.textColor = new Color(0.9f, 0.9f, 0.9f); _toggleStyle.onNormal.textColor = Color.white; _labelStyle = new GUIStyle(GUI.skin.label); _labelStyle.fontSize = 11; _labelStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f); _btnStyle = new GUIStyle(GUI.skin.button); _btnStyle.fontSize = 12; _btnStyle.fontStyle = (FontStyle)1; } } private Texture2D MakeSolidTexture(int width, int height, Color col) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = col; } Texture2D val = new Texture2D(width, height); val.SetPixels(array); val.Apply(); return val; } private void OnGUI() { //IL_002d: 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_004e: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (_isOpen) { ZCursor.LockState = (CursorLockMode)0; ZCursor.Show(); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; InitStyles(); _windowRect = GUI.Window(9928374, _windowRect, new WindowFunction(DrawWindowContents), "Gatherer - Resource Tracking Menu", _winStyle); GUI.BringWindowToFront(9928374); } } private void DrawWindowContents(int windowId) { //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014a: 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_0256: Unknown result type (might be due to invalid IL or missing references) GathererConfig config = GathererManager.Instance.Config; if (config == null) { return; } GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Resources & Categories", (_activeTab == 0) ? _tabActiveStyle : _tabStyle, Array.Empty<GUILayoutOption>())) { _activeTab = 0; } if (GUILayout.Button("General & Pin Settings", (_activeTab == 1) ? _tabActiveStyle : _tabStyle, Array.Empty<GUILayoutOption>())) { _activeTab = 1; } if (GUILayout.Button("Custom Filters & Radar", (_activeTab == 2) ? _tabActiveStyle : _tabStyle, Array.Empty<GUILayoutOption>())) { _activeTab = 2; } if (GUILayout.Button("Close (Esc)", _btnStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { _isOpen = false; TakeInputFocus(focus: false); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Map Icons & Visibility", (_activeTab == 3) ? _tabActiveStyle : _tabStyle, Array.Empty<GUILayoutOption>())) { _activeTab = 3; } GUILayout.EndHorizontal(); GUILayout.Space(8f); _scrollPos = GUILayout.BeginScrollView(_scrollPos, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); if (_activeTab == 0) { DrawResourcesTab(config); } else if (_activeTab == 1) { DrawGeneralSettingsTab(config); } else if (_activeTab == 2) { DrawFiltersTab(config); } else { DrawMapDisplayTab(config); } GUILayout.EndScrollView(); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Press " + ((object)config.ToggleUiKey.Value/*cast due to .constrained prefix*/).ToString() + " or Esc to toggle this menu anytime in-game.", _labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); if (_activeTab != 3) { if (GUILayout.Button("Select All", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { SetAllResources(config, val: true); } if (GUILayout.Button("Deselect All", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { SetAllResources(config, val: false); } } GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 24f)); } private void DrawResourcesTab(GathererConfig config) { GUILayout.Label("Categories & Specific Resource Toggles", _headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("Select the resources you want to track below. All resources are disabled by default until selected.", _labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); DrawCategoryHeader("Mushrooms", config.TrackMushrooms, delegate(bool val) { config.MarkRedMushroom.Value = val; config.MarkYellowMushroom.Value = val; config.MarkBlueMushroom.Value = val; config.MarkMagecap.Value = val; config.MarkJotunPuffs.Value = val; config.MarkSmokePuff.Value = val; }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkRedMushroom, "Red Mushroom", config.TrackMushrooms); DrawToggle(config.MarkYellowMushroom, "Yellow Mushroom", config.TrackMushrooms); DrawToggle(config.MarkBlueMushroom, "Blue Mushroom", config.TrackMushrooms); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkMagecap, "Magecap (Mistlands)", config.TrackMushrooms); DrawToggle(config.MarkJotunPuffs, "Jotun Puffs (Mistlands)", config.TrackMushrooms); DrawToggle(config.MarkSmokePuff, "Smoke Puff (Ashlands)", config.TrackMushrooms); GUILayout.EndHorizontal(); GUILayout.Space(6f); DrawCategoryHeader("Berries", config.TrackBerries, delegate(bool val) { config.MarkRaspberry.Value = val; config.MarkBlueberry.Value = val; config.MarkCloudberry.Value = val; }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkRaspberry, "Raspberries", config.TrackBerries); DrawToggle(config.MarkBlueberry, "Blueberries", config.TrackBerries); DrawToggle(config.MarkCloudberry, "Cloudberries", config.TrackBerries); GUILayout.EndHorizontal(); GUILayout.Space(6f); DrawCategoryHeader("Herbs & Wild Plants", config.TrackHerbsAndPlants, delegate(bool val) { config.MarkThistle.Value = val; config.MarkDandelion.Value = val; config.MarkCarrotSeeds.Value = val; config.MarkTurnipSeeds.Value = val; config.MarkOnionSeeds.Value = val; config.MarkFiddlehead.Value = val; }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkThistle, "Thistle", config.TrackHerbsAndPlants); DrawToggle(config.MarkDandelion, "Dandelion", config.TrackHerbsAndPlants); DrawToggle(config.MarkCarrotSeeds, "Carrot Seeds", config.TrackHerbsAndPlants); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkTurnipSeeds, "Turnip Seeds", config.TrackHerbsAndPlants); DrawToggle(config.MarkOnionSeeds, "Onion Seeds", config.TrackHerbsAndPlants); DrawToggle(config.MarkFiddlehead, "Fiddlehead (Ashlands)", config.TrackHerbsAndPlants); GUILayout.EndHorizontal(); GUILayout.Space(6f); DrawCategoryHeader("Farming Crops (Wild)", config.TrackFarmingCrops, delegate(bool val) { config.MarkBarley.Value = val; config.MarkFlax.Value = val; }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkBarley, "Barley", config.TrackFarmingCrops); DrawToggle(config.MarkFlax, "Flax", config.TrackFarmingCrops); GUILayout.EndHorizontal(); GUILayout.Space(6f); DrawCategoryHeader("Ores & Mineral Veins", config.TrackOresAndVeins, delegate(bool val) { config.MarkCopper.Value = val; config.MarkTin.Value = val; config.MarkSilver.Value = val; config.MarkIronMudpile.Value = val; config.MarkObsidian.Value = val; config.MarkFlametal.Value = val; }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkCopper, "Copper Deposit", config.TrackOresAndVeins); DrawToggle(config.MarkTin, "Tin Deposit", config.TrackOresAndVeins); DrawToggle(config.MarkSilver, "Silver Vein", config.TrackOresAndVeins); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkIronMudpile, "Muddy Scrap Pile (Iron)", config.TrackOresAndVeins); DrawToggle(config.MarkObsidian, "Obsidian", config.TrackOresAndVeins); DrawToggle(config.MarkFlametal, "Flametal (Ashlands)", config.TrackOresAndVeins); GUILayout.EndHorizontal(); GUILayout.Space(6f); DrawCategoryHeader("Special POIs & Beehives", config.TrackSpecial, delegate(bool val) { config.MarkBeehive.Value = val; config.MarkVegvisir.Value = val; config.MarkSurtlingSpawner.Value = val; config.MarkAncientRoot.Value = val; }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkBeehive, "Wild Beehives", config.TrackSpecial); DrawToggle(config.MarkVegvisir, "Vegvisir Runestones", config.TrackSpecial); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawToggle(config.MarkSurtlingSpawner, "Surtling Spawners", config.TrackSpecial); DrawToggle(config.MarkAncientRoot, "Ancient Roots (Sap)", config.TrackSpecial); GUILayout.EndHorizontal(); } private void DrawMapDisplayTab(GathererConfig config) { GUILayout.Label("Map Icons & Visibility", _headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("Hide/show existing resource pins without changing tracking or sharing.", _labelStyle, Array.Empty<GUILayoutOption>()); DrawToggle(config.UseResourceIcons, "Use item icons"); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Show All", Array.Empty<GUILayoutOption>())) { foreach (ConfigEntry<bool> value in config.ResourceVisibility.Values) { value.Value = true; } } if (GUILayout.Button("Hide All", Array.Empty<GUILayoutOption>())) { foreach (ConfigEntry<bool> value2 in config.ResourceVisibility.Values) { value2.Value = false; } } GUILayout.EndHorizontal(); foreach (ResourceDefinition allDefinition in ResourceRegistry.AllDefinitions) { DrawToggle(config.ResourceVisibility[allDefinition.Id], allDefinition.DisplayName); } GUILayout.Label("Filters apply to world map and minimap. Other players keep their own visibility choices.", _labelStyle, Array.Empty<GUILayoutOption>()); } private void DrawGeneralSettingsTab(GathererConfig config) { GUILayout.Label("General & Cartography Table Settings", _headerStyle, Array.Empty<GUILayoutOption>()); config.Enabled.Value = GUILayout.Toggle(config.Enabled.Value, " Mod Enabled (Master Toggle)", _toggleStyle, Array.Empty<GUILayoutOption>()); config.ShareOnCartographyTable.Value = GUILayout.Toggle(config.ShareOnCartographyTable.Value, " Share On Cartography Table (Synchronize pins with other players via Cartography Table)", _toggleStyle, Array.Empty<GUILayoutOption>()); config.ShowHudNotification.Value = GUILayout.Toggle(config.ShowHudNotification.Value, " Show HUD Notifications (Top-left on-screen message when a node is marked)", _toggleStyle, Array.Empty<GUILayoutOption>()); config.CleanPinNames.Value = GUILayout.Toggle(config.CleanPinNames.Value, " Clean Pin Names (e.g. 'Copper' instead of 'Copper deposit')", _toggleStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(10f); GUILayout.Label("Discovery Mode", _sectionStyle, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Toggle(config.Mode.Value == DiscoveryMode.InteractAndProximity, " Interact & Proximity Radar (Default)", _toggleStyle, Array.Empty<GUILayoutOption>())) { config.Mode.Value = DiscoveryMode.InteractAndProximity; } if (GUILayout.Toggle(config.Mode.Value == DiscoveryMode.InteractOnly, " Interact Only (Classic WoW)", _toggleStyle, Array.Empty<GUILayoutOption>())) { config.Mode.Value = DiscoveryMode.InteractOnly; } if (GUILayout.Toggle(config.Mode.Value == DiscoveryMode.InteractAndHover, " Interact & Hover", _toggleStyle, Array.Empty<GUILayoutOption>())) { config.Mode.Value = DiscoveryMode.InteractAndHover; } if (GUILayout.Toggle(config.Mode.Value == DiscoveryMode.All, " All Triggers", _toggleStyle, Array.Empty<GUILayoutOption>())) { config.Mode.Value = DiscoveryMode.All; } GUILayout.EndHorizontal(); GUILayout.Space(10f); GUILayout.Label("Pin Label Prefix", _sectionStyle, Array.Empty<GUILayoutOption>()); config.PinNamePrefix.Value = GUILayout.TextField(config.PinNamePrefix.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) }); GUILayout.Label("Optional prefix added before names on the map (e.g. '[G] ' or empty).", _labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(10f); GUILayout.Label("Duplicate Merging Distances (Radius in Meters)", _sectionStyle, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label($"Herb/Mushroom Radius: {config.HerbMergeRadius.Value:F0}m", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }); config.HerbMergeRadius.Value = GUILayout.HorizontalSlider(config.HerbMergeRadius.Value, 2f, 30f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label($"Ore Deposit Radius: {config.OreMergeRadius.Value:F0}m", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }); config.OreMergeRadius.Value = GUILayout.HorizontalSlider(config.OreMergeRadius.Value, 5f, 50f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }); GUILayout.EndHorizontal(); } private void DrawFiltersTab(GathererConfig config) { GUILayout.Label("Advanced Filters & Radar Tuning", _headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("Proximity Radar Sweep Radius", _sectionStyle, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label($"{config.ProximityRadius.Value:F0} meters", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }); config.ProximityRadius.Value = GUILayout.HorizontalSlider(config.ProximityRadius.Value, 5f, 40f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) }); GUILayout.EndHorizontal(); GUILayout.Space(10f); GUILayout.Label("Custom Exclude Filter (Blacklist)", _sectionStyle, Array.Empty<GUILayoutOption>()); config.CustomExcludeFilter.Value = GUILayout.TextField(config.CustomExcludeFilter.Value, Array.Empty<GUILayoutOption>()); GUILayout.Label("Comma-separated prefab or display names to NEVER mark (e.g. 'Copper, Flint, Stone').", _labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(10f); GUILayout.Label("Custom Include Filter (Whitelist / Modded items)", _sectionStyle, Array.Empty<GUILayoutOption>()); config.CustomIncludeFilter.Value = GUILayout.TextField(config.CustomIncludeFilter.Value, Array.Empty<GUILayoutOption>()); GUILayout.Label("Comma-separated prefab names to ALWAYS mark even from other mods (e.g. 'DragonEgg, GoblinTotem').", _labelStyle, Array.Empty<GUILayoutOption>()); } private void DrawCategoryHeader(string title, ConfigEntry<bool> entry, Action<bool>? onToggleAll = null) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(title, _sectionStyle, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); bool flag = GUILayout.Toggle(entry.Value, entry.Value ? "Category: ON " : "Category: OFF", _toggleStyle, Array.Empty<GUILayoutOption>()); if (flag != entry.Value) { entry.Value = flag; onToggleAll?.Invoke(flag); } GUILayout.EndHorizontal(); } private void DrawToggle(ConfigEntry<bool> entry, string label, ConfigEntry<bool>? parentCategory = null) { bool value = entry.Value; bool flag = GUILayout.Toggle(value, " " + label, _toggleStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(210f) }); if (flag != value) { entry.Value = flag; if (flag && parentCategory != null && !parentCategory.Value) { parentCategory.Value = true; } } } private void SetAllResources(GathererConfig config, bool val) { config.TrackMushrooms.Value = val; config.TrackBerries.Value = val; config.TrackHerbsAndPlants.Value = val; config.TrackFarmingCrops.Value = val; config.TrackOresAndVeins.Value = val; config.TrackBeehives.Value = val; config.TrackSpecial.Value = val; config.MarkRedMushroom.Value = val; config.MarkYellowMushroom.Value = val; config.MarkBlueMushroom.Value = val; config.MarkMagecap.Value = val; config.MarkJotunPuffs.Value = val; config.MarkSmokePuff.Value = val; config.MarkRaspberry.Value = val; config.MarkBlueberry.Value = val; config.MarkCloudberry.Value = val; config.MarkThistle.Value = val; config.MarkDandelion.Value = val; config.MarkCarrotSeeds.Value = val; config.MarkTurnipSeeds.Value = val; config.MarkOnionSeeds.Value = val; config.MarkFiddlehead.Value = val; config.MarkBarley.Value = val; config.MarkFlax.Value = val; config.MarkCopper.Value = val; config.MarkTin.Value = val; config.MarkSilver.Value = val; config.MarkIronMudpile.Value = val; config.MarkObsidian.Value = val; config.MarkFlametal.Value = val; config.MarkBeehive.Value = val; config.MarkVegvisir.Value = val; config.MarkSurtlingSpawner.Value = val; config.MarkAncientRoot.Value = val; } private void OnDestroy() { if ((Object)(object)_bgTexture != (Object)null) { Object.Destroy((Object)(object)_bgTexture); } if ((Object)(object)_tabActiveTexture != (Object)null) { Object.Destroy((Object)(object)_tabActiveTexture); } } } public static class GathererNotifications { public static void ShowMarked(string resourceName) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { string text = "[Gatherer] Marked " + resourceName; ((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } } } public static class ResourcePinPresentation { private readonly struct VisualState { public readonly GameObject Marker; public readonly GameObject? Label; public readonly bool MarkerActive; public readonly bool LabelActive; public readonly Image Image; public readonly Sprite Sprite; public VisualState(PinData pin) { Marker = ((Component)pin.m_uiElement).gameObject; PinNameData namePinData = pin.m_NamePinData; Label = ((namePinData != null) ? namePinData.PinNameGameObject : null); MarkerActive = Marker.activeSelf; LabelActive = (Object)(object)Label != (Object)null && Label.activeSelf; Image = pin.m_iconElement; Sprite = Image.sprite; } public void Restore() { if ((Object)(object)Marker != (Object)null) { Marker.SetActive(MarkerActive); } if ((Object)(object)Label != (Object)null) { Label.SetActive(LabelActive); } if ((Object)(object)Image != (Object)null) { Image.sprite = Sprite; } } } private static readonly Dictionary<string, string> ItemNames = new Dictionary<string, string> { ["Mushroom"] = "Mushroom", ["MushroomYellow"] = "MushroomYellow", ["MushroomBlue"] = "MushroomBlue", ["Magecap"] = "MushroomMagecap", ["JotunPuffs"] = "MushroomJotunPuffs", ["SmokePuff"] = "MushroomSmokePuff", ["Raspberry"] = "Raspberry", ["Blueberry"] = "Blueberries", ["Cloudberry"] = "Cloudberry", ["Thistle"] = "Thistle", ["Dandelion"] = "Dandelion", ["CarrotSeeds"] = "CarrotSeeds", ["TurnipSeeds"] = "TurnipSeeds", ["OnionSeeds"] = "OnionSeeds", ["Fiddlehead"] = "Fiddleheadfern", ["Barley"] = "Barley", ["Flax"] = "Flax", ["Copper"] = "CopperOre", ["Tin"] = "TinOre", ["Silver"] = "SilverOre", ["IronMudpile"] = "IronScrap", ["Obsidian"] = "Obsidian", ["Flametal"] = "FlametalOreNew", ["Beehive"] = "QueenBee" }; private static readonly FieldRef<Minimap, List<PinData>> Pins = AccessTools.FieldRefAccess<Minimap, List<PinData>>("m_pins"); private static readonly Dictionary<string, ResourceDefinition?> Matches = new Dictionary<string, ResourceDefinition>(); private static readonly Dictionary<string, Sprite?> Icons = new Dictionary<string, Sprite>(); private static readonly List<VisualState> Changed = new List<VisualState>(); private static ObjectDB? _database; private static string _prefix = ""; public static void Restore() { foreach (VisualState item in Changed) { item.Restore(); } Changed.Clear(); } public static void Reset() { Restore(); Matches.Clear(); Icons.Clear(); _database = null; _prefix = ""; } public static void Apply(Minimap map) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Invalid comparison between Unknown and I4 //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Invalid comparison between Unknown and I4 //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Invalid comparison between Unknown and I4 //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Invalid comparison between Unknown and I4 GathererConfig config = GathererManager.Instance.Config; if (config == null || !config.Enabled.Value) { return; } if (_prefix != config.PinNamePrefix.Value) { _prefix = config.PinNamePrefix.Value; Matches.Clear(); } if ((Object)(object)_database != (Object)(object)ObjectDB.instance) { _database = ObjectDB.instance; Icons.Clear(); } foreach (PinData item in Pins.Invoke(map)) { if (((int)item.m_type != 0 && (int)item.m_type != 1 && (int)item.m_type != 2 && (int)item.m_type != 3 && (int)item.m_type != 6) || (Object)(object)item.m_uiElement == (Object)null || (Object)(object)item.m_iconElement == (Object)null) { continue; } if (!Matches.TryGetValue(item.m_name, out ResourceDefinition value)) { value = ResourcePinMatcher.Match(item.m_name, (string key) => (Localization.instance == null) ? key : Localization.instance.Localize(key), _prefix); if (Matches.Count >= 4096) { Matches.Clear(); } Matches[item.m_name] = value; } if (value == null) { continue; } ConfigEntry<bool> value2; bool flag = !config.ResourceVisibility.TryGetValue(value.Id, out value2) || value2.Value; Sprite val = (config.UseResourceIcons.Value ? GetIcon(value) : null); if (flag && (Object)(object)val == (Object)null) { continue; } Changed.Add(new VisualState(item)); if (!flag) { ((Component)item.m_uiElement).gameObject.SetActive(false); PinNameData namePinData = item.m_NamePinData; if ((Object)(object)((namePinData != null) ? namePinData.PinNameGameObject : null) != (Object)null) { item.m_NamePinData.PinNameGameObject.SetActive(false); } } else { item.m_iconElement.sprite = val; } } } private static Sprite? GetIcon(ResourceDefinition resource) { if ((Object)(object)_database == (Object)null) { return null; } if (Icons.TryGetValue(resource.Id, out Sprite value)) { return value; } if (ItemNames.TryGetValue(resource.Id, out string value2)) { GameObject itemPrefab = _database.GetItemPrefab(value2); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val != (Object)null && val.m_itemData.m_shared.m_icons.Length != 0) { value = val.m_itemData.GetIcon(); } } Icons[resource.Id] = value; return value; } } } namespace ValheimGatherer.Patches { [HarmonyPatch(typeof(Destructible), "Damage")] public static class DestructiblePatch { [HarmonyPrefix] public static void Prefix(Destructible __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null) && hit != null && (Object)(object)hit.GetAttacker() == (Object)(object)Player.m_localPlayer) { GathererManager.Instance.TryProcessGameObject(((Component)__instance).gameObject); } } } [HarmonyPatch(typeof(Player), "UpdateHover")] public static class HoverPatch { private static float _lastHoverProcessTime; [HarmonyPostfix] public static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } GathererConfig config = GathererManager.Instance.Config; if (config == null || !config.Enabled.Value) { return; } DiscoveryMode value = config.Mode.Value; if ((value == DiscoveryMode.InteractAndHover || value == DiscoveryMode.All) && !(Time.time - _lastHoverProcessTime < 0.5f)) { GameObject hoverObject = ((Humanoid)__instance).GetHoverObject(); if ((Object)(object)hoverObject != (Object)null) { _lastHoverProcessTime = Time.time; GathererManager.Instance.TryProcessGameObject(hoverObject); } } } } [HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")] public static class GameCameraMouseCapturePatch { [HarmonyPrefix] public static bool Prefix() { if (GathererInGameUI.IsMenuOpen) { ZCursor.LockState = (CursorLockMode)0; ZCursor.Show(); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; return false; } return true; } } [HarmonyPatch(typeof(PlayerController), "TakeInput", new Type[] { typeof(bool) })] public static class PlayerControllerTakeInputPatch { [HarmonyPrefix] public static bool Prefix(ref bool __result) { if (GathererInGameUI.IsMenuOpen) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(Player), "TakeInput")] public static class PlayerTakeInputPatch { [HarmonyPrefix] public static bool Prefix(ref bool __result) { if (GathererInGameUI.IsMenuOpen) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(MapTable), "OnWrite")] public static class MapTableOnWritePatch { [HarmonyPostfix] public static void Postfix(MapTable __instance, Switch caller, Humanoid user, ItemData item) { if (!((Object)(object)user != (Object)(object)Player.m_localPlayer)) { GathererConfig config = GathererManager.Instance.Config; if (config != null && config.ShareOnCartographyTable.Value && config.ShowHudNotification.Value) { ((Character)Player.m_localPlayer).Message((MessageType)1, "[Gatherer] Map discoveries and resource pins recorded to Cartography Table!", 0, (Sprite)null, false); } } } } [HarmonyPatch(typeof(MapTable), "OnRead", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData), typeof(bool) })] public static class MapTableOnReadPatch { [HarmonyPostfix] public static void Postfix(MapTable __instance, Switch caller, Humanoid user, ItemData item, bool showMessage) { if (!((Object)(object)user != (Object)(object)Player.m_localPlayer) && showMessage) { GathererConfig config = GathererManager.Instance.Config; if (config != null && config.ShareOnCartographyTable.Value && config.ShowHudNotification.Value) { ((Character)Player.m_localPlayer).Message((MessageType)1, "[Gatherer] Synchronized map discoveries and resource pins from Cartography Table!", 0, (Sprite)null, false); } } } } [HarmonyPatch(typeof(MineRock), "Damage")] public static class MineRockPatch { [HarmonyPrefix] public static void Prefix(MineRock __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null) && hit != null && (Object)(object)hit.GetAttacker() == (Object)(object)Player.m_localPlayer) { GathererManager.Instance.TryProcessGameObject(((Component)__instance).gameObject); } } } [HarmonyPatch(typeof(MineRock5), "Damage")] public static class MineRock5Patch { [HarmonyPrefix] public static void Prefix(MineRock5 __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null) && hit != null && (Object)(object)hit.GetAttacker() == (Object)(object)Player.m_localPlayer) { GathererManager.Instance.TryProcessGameObject(((Component)__instance).gameObject); } } } [HarmonyPatch(typeof(Minimap), "UpdatePins")] public static class MinimapPresentationPatch { [HarmonyPrefix] public static void Prefix() { ResourcePinPresentation.Restore(); } [HarmonyPostfix] public static void Postfix(Minimap __instance) { ResourcePinPresentation.Apply(__instance); } } [HarmonyPatch(typeof(Minimap), "OnDestroy")] public static class MinimapPresentationCleanupPatch { [HarmonyPrefix] public static void Prefix() { ResourcePinPresentation.Reset(); } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] public static class ResourcePinLanguagePatch { [HarmonyPostfix] public static void Postfix() { ResourcePinPresentation.Reset(); } } [HarmonyPatch(typeof(Pickable), "Interact")] public static class PickablePatch { [HarmonyPrefix] public static void Prefix(Pickable __instance, Humanoid character) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)character == (Object)null) && !((Object)(object)character != (Object)(object)Player.m_localPlayer)) { GathererManager.Instance.TryProcessGameObject(((Component)__instance).gameObject); } } } } namespace ValheimGatherer.Core { public struct PinCheckData { public Vector3 Position; public string Name; public PinCheckData(Vector3 position, string name) { //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) Position = position; Name = name; } } public class GathererManager { private static GathererManager? _instance; private static readonly FieldRef<Minimap, List<PinData>> PinsFieldRef = AccessTools.FieldRefAccess<Minimap, List<PinData>>("m_pins"); private ManualLogSource? _logger; public static GathererManager Instance => _instance ?? (_instance = new GathererManager()); public GathererConfig? Config { get; private set; } public void Initialize(GathererConfig config, ManualLogSource logger) { Config = config; _logger = logger; } public bool TryProcessGameObject(GameObject? go, Vector3? explicitPos = null) { //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Unknown result type (might be due to invalid IL or missing references) //IL_03ed: Unknown result type (might be due to invalid IL or missing references) //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Unknown result type (might be due to invalid IL or missing references) //IL_0420: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null || Config == null || !Config.Enabled.Value) { return false; } if ((Object)(object)Minimap.instance == (Object)null) { return false; } Piece val = go.GetComponentInParent<Piece>() ?? go.GetComponent<Piece>(); if ((Object)(object)val != (Object)null && val.IsPlacedByPlayer()) { return false; } string name = ((Object)go).name; string text = (name.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase) ? name.Substring(0, name.Length - 7) : name); string text2 = ""; string text3 = ""; Pickable val2 = go.GetComponentInParent<Pickable>() ?? go.GetComponent<Pickable>(); if ((Object)(object)val2 != (Object)null) { if ((Object)(object)val2.m_itemPrefab != (Object)null) { text3 = ((Object)val2.m_itemPrefab).name; } text2 = val2.GetHoverName(); } if (string.IsNullOrEmpty(text2)) { MineRock val3 = go.GetComponentInParent<MineRock>() ?? go.GetComponent<MineRock>(); if ((Object)(object)val3 != (Object)null) { text2 = val3.m_name; } } if (string.IsNullOrEmpty(text2)) { MineRock5 val4 = go.GetComponentInParent<MineRock5>() ?? go.GetComponent<MineRock5>(); if ((Object)(object)val4 != (Object)null) { text2 = val4.m_name; } } if (string.IsNullOrEmpty(text2)) { Beehive val5 = go.GetComponentInParent<Beehive>() ?? go.GetComponent<Beehive>(); if ((Object)(object)val5 != (Object)null) { text2 = val5.m_name; } } if (string.IsNullOrEmpty(text2)) { Hoverable val6 = go.GetComponentInParent<Hoverable>() ?? go.GetComponent<Hoverable>(); if (val6 != null) { text2 = val6.GetHoverText(); } } string text4 = text2; if (!string.IsNullOrEmpty(text2) && Localization.instance != null) { text4 = Localization.instance.Localize(text2); } ResourceDefinition resourceDefinition = null; if (!string.IsNullOrEmpty(text3)) { resourceDefinition = ResourceRegistry.Match(text3, text2, text4); } if (resourceDefinition == null) { resourceDefinition = ResourceRegistry.Match(text, text2, text4); } if (resourceDefinition == null) { if (!Config.IsCustomIncluded(text) && (string.IsNullOrEmpty(text3) || !Config.IsCustomIncluded(text3)) && (string.IsNullOrEmpty(text4) || !Config.IsCustomIncluded(text4))) { return false; } string text5 = ((!string.IsNullOrEmpty(text4)) ? text4 : text); resourceDefinition = new ResourceDefinition(text, text5, text5, ResourceCategory.Custom, Config.DefaultPinIcon.Value, new string[1] { text }, new string[0], Config.HerbMergeRadius.Value, (GathererConfig cfg) => true); } if (!ResourceRegistry.IsEnabled(resourceDefinition, Config, text, text4)) { return false; } Vector3 val7 = (Vector3)(((??)explicitPos) ?? go.transform.position); float value; PinIconType value2; if (resourceDefinition.Category == ResourceCategory.OresAndVeins) { value = Config.OreMergeRadius.Value; value2 = Config.OrePinIcon.Value; } else if (resourceDefinition.Category == ResourceCategory.Special || resourceDefinition.Category == ResourceCategory.Beehives) { value = Config.SpecialMergeRadius.Value; value2 = Config.SpecialPinIcon.Value; } else { value = Config.HerbMergeRadius.Value; value2 = Config.DefaultPinIcon.Value; } string text6 = (Config.CleanPinNames.Value ? resourceDefinition.CleanShortName : resourceDefinition.DisplayName); if (!string.IsNullOrEmpty(text4) && !Config.CleanPinNames.Value) { text6 = text4; } string text7 = (string.IsNullOrEmpty(Config.PinNamePrefix.Value) ? text6 : (Config.PinNamePrefix.Value + text6)); if (IsDuplicateExistingPin(val7, text6, value)) { return false; } bool value3 = Config.ShareOnCartographyTable.Value; long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0); PlatformUserID none = PlatformUserID.None; if (Minimap.instance.AddPin(val7, (PinType)value2, text7, value3, false, num, none) != null) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogInfo((object)$"[Gatherer] Added pin '{text7}' at {val7} (shared via Cartography: {value3})"); } if (Config.ShowHudNotification.Value) { GathererNotifications.ShowMarked(text6); } return true; } return false; } public bool IsDuplicateExistingPin(Vector3 pos, string resourceName, float radius) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null) { return false; } List<PinData> list = PinsFieldRef.Invoke(Minimap.instance); if (list == null) { return false; } float num = radius * radius; for (int i = 0; i < list.Count; i++) { PinData val = list[i]; if (val != null) { float num2 = pos.x - val.m_pos.x; float num3 = pos.z - val.m_pos.z; if (num2 * num2 + num3 * num3 <= num && (string.Equals(val.m_name, resourceName, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrEmpty(val.m_name) && val.m_name.IndexOf(resourceName, StringComparison.OrdinalIgnoreCase) >= 0) || (!string.IsNullOrEmpty(resourceName) && resourceName.IndexOf(val.m_name, StringComparison.OrdinalIgnoreCase) >= 0))) { return true; } } } return false; } public static bool CheckDuplicate(Vector3 pos, string resourceName, IEnumerable<PinCheckData> existingPins, float radius) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) float num = radius * radius; foreach (PinCheckData existingPin in existingPins) { float num2 = pos.x - existingPin.Position.x; float num3 = pos.z - existingPin.Position.z; if (num2 * num2 + num3 * num3 <= num && (string.Equals(existingPin.Name, resourceName, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrEmpty(existingPin.Name) && existingPin.Name.IndexOf(resourceName, StringComparison.OrdinalIgnoreCase) >= 0) || (!string.IsNullOrEmpty(resourceName) && resourceName.IndexOf(existingPin.Name, StringComparison.OrdinalIgnoreCase) >= 0))) { return true; } } return false; } } public class ProximityScanner : MonoBehaviour { private readonly Collider[] _hitBuffer = (Collider[])(object)new Collider[128]; private readonly HashSet<int> _scannedObjectIds = new HashSet<int>(); private void Start() { ((MonoBehaviour)this).StartCoroutine(ScanRoutine()); } private IEnumerator ScanRoutine() { while (true) { GathererConfig config = GathererManager.Instance.Config; if (config == null || !config.Enabled.Value) { yield return (object)new WaitForSeconds(3f); continue; } DiscoveryMode value = config.Mode.Value; if (value != DiscoveryMode.InteractAndProximity && value != DiscoveryMode.All) { yield return (object)new WaitForSeconds(3f); continue; } if ((Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead() || (Object)(object)Minimap.instance == (Object)null) { yield return (object)new WaitForSeconds(2f); continue; } PerformScan(config.ProximityRadius.Value); float num = Mathf.Max(1f, config.ProximityScanInterval.Value); yield return (object)new WaitForSeconds(num); } } private void PerformScan(float radius) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) int num = Physics.OverlapSphereNonAlloc(((Component)Player.m_localPlayer).transform.position, radius, _hitBuffer); _scannedObjectIds.Clear(); for (int i = 0; i < num; i++) { Collider val = _hitBuffer[i]; if (!((Object)(object)val == (Object)null)) { GameObject gameObject = ((Component)val).gameObject; int instanceID = ((Object)gameObject).GetInstanceID(); if (!_scannedObjectIds.Contains(instanceID)) { _scannedObjectIds.Add(instanceID); GathererManager.Instance.TryProcessGameObject(gameObject); } } } } } public class ResourceDefinition { public string Id { get; } public string DisplayName { get; } public string CleanShortName { get; } public ResourceCategory Category { get; } public PinIconType DefaultIcon { get; } public string[] PrefabNames { get; } public string[] LocalizationKeys { get; } public float DefaultMergeRadius { get; } public Func<GathererConfig, bool> IsEnabledFunc { get; } public ResourceDefinition(string id, string displayName, string cleanShortName, ResourceCategory category, PinIconType defaultIcon, string[] prefabNames, string[] localizationKeys, float defaultMergeRadius, Func<GathererConfig, bool> isEnabledFunc) { Id = id; DisplayName = displayName; CleanShortName = cleanShortName; Category = category; DefaultIcon = defaultIcon; PrefabNames = prefabNames; LocalizationKeys = localizationKeys; DefaultMergeRadius = defaultMergeRadius; IsEnabledFunc = isEnabledFunc; } public bool Matches(string prefabName, string hoverName, string localizedName) { if (!string.IsNullOrEmpty(hoverName)) { string[] localizationKeys = LocalizationKeys; foreach (string b in localizationKeys) { if (string.Equals(hoverName, b, StringComparison.OrdinalIgnoreCase)) { return true; } } } if (!string.IsNullOrEmpty(prefabName)) { string[] localizationKeys = PrefabNames; foreach (string b2 in localizationKeys) { if (string.Equals(prefabName, b2, StringComparison.OrdinalIgnoreCase)) { return true; } } } if (!string.IsNullOrEmpty(localizedName) && (string.Equals(localizedName, DisplayName, StringComparison.OrdinalIgnoreCase) || string.Equals(localizedName, CleanShortName, StringComparison.OrdinalIgnoreCase))) { return true; } if (!string.IsNullOrEmpty(prefabName)) { string[] localizationKeys = PrefabNames; foreach (string text in localizationKeys) { if (prefabName.StartsWith(text + "_", StringComparison.OrdinalIgnoreCase) || prefabName.StartsWith(text + "(", StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } public static class ResourcePinMatcher { public static ResourceDefinition? Match(string? name, Func<string, string>? localize = null, string prefix = "") { if (string.IsNullOrWhiteSpace(name)) { return null; } if (!string.IsNullOrEmpty(prefix) && name.StartsWith(prefix, StringComparison.Ordinal)) { name = name.Substring(prefix.Length); } ResourceDefinition result = null; int longestMatch = 0; foreach (ResourceDefinition allDefinition in ResourceRegistry.AllDefinitions) { Check(allDefinition, allDefinition.Id); Check(allDefinition, allDefinition.DisplayName); Check(allDefinition, allDefinition.CleanShortName); string[] localizationKeys = allDefinition.LocalizationKeys; foreach (string text in localizationKeys) { Check(allDefinition, text); if (localize != null) { Check(allDefinition, localize(text)); } } } return result; void Check(ResourceDefinition resource, string candidate) { if (!string.IsNullOrEmpty(candidate) && candidate.Length > longestMatch && name.EndsWith(candidate, StringComparison.OrdinalIgnoreCase)) { int num = name.Length - candidate.Length; if (num == 0 || char.IsWhiteSpace(name[num - 1]) || name[num - 1] == ']') { longestMatch = candidate.Length; result = resource; } } } } } public class ResourceRegistry { private static readonly List<ResourceDefinition> Definitions; public static IReadOnlyList<ResourceDefinition> AllDefinitions => Definitions; static ResourceRegistry() { Definitions = new List<ResourceDefinition>(); RegisterDefaults(); } private static void RegisterDefaults() { Register(new ResourceDefinition("MushroomYellow", "Yellow Mushroom", "Yellow Mushroom", ResourceCategory.Mushrooms, PinIconType.Dot, new string[2] { "Pickable_Mushroom_yellow", "MushroomYellow" }, new string[1] { "$item_mushroomyellow" }, 10f, (GathererConfig cfg) => cfg.TrackMushrooms.Value && cfg.MarkYellowMushroom.Value)); Register(new ResourceDefinition("MushroomBlue", "Blue Mushroom", "Blue Mushroom", ResourceCategory.Mushrooms, PinIconType.Dot, new string[2] { "Pickable_Mushroom_blue", "MushroomBlue" }, new string[1] { "$item_mushroomblue" }, 10f, (GathererConfig cfg) => cfg.TrackMushrooms.Value && cfg.MarkBlueMushroom.Value)); Register(new ResourceDefinition("Magecap", "Magecap", "Magecap", ResourceCategory.Mushrooms, PinIconType.Dot, new string[2] { "Pickable_Magecap", "Magecap" }, new string[1] { "$item_magecap" }, 10f, (GathererConfig cfg) => cfg.TrackMushrooms.Value && cfg.MarkMagecap.Value)); Register(new ResourceDefinition("JotunPuffs", "Jotun Puffs", "Jotun Puffs", ResourceCategory.Mushrooms, PinIconType.Dot, new string[2] { "Pickable_JotunPuffs", "JotunPuffs" }, new string[1] { "$item_jotunpuffs" }, 10f, (GathererConfig cfg) => cfg.TrackMushrooms.Value && cfg.MarkJotunPuffs.Value)); Register(new ResourceDefinition("SmokePuff", "Smoke Puff", "Smoke Puff", ResourceCategory.Mushrooms, PinIconType.Dot, new string[2] { "Pickable_SmokePuff", "SmokePuff" }, new string[1] { "$item_smokepuff" }, 10f, (GathererConfig cfg) => cfg.TrackMushrooms.Value && cfg.MarkSmokePuff.Value)); Register(new ResourceDefinition("Mushroom", "Mushroom", "Mushroom", ResourceCategory.Mushrooms, PinIconType.Dot, new string[2] { "Pickable_Mushroom", "Mushroom" }, new string[1] { "$item_mushroom" }, 10f, (GathererConfig cfg) => cfg.TrackMushrooms.Value && cfg.MarkRedMushroom.Value)); Register(new ResourceDefinition("Raspberry", "Raspberries", "Raspberries", ResourceCategory.Berries, PinIconType.Dot, new string[3] { "Pickable_Raspberry", "Raspberry", "Raspberries" }, new string[1] { "$item_raspberries" }, 10f, (GathererConfig cfg) => cfg.TrackBerries.Value && cfg.MarkRaspberry.Value)); Register(new ResourceDefinition("Blueberry", "Blueberries", "Blueberries", ResourceCategory.Berries, PinIconType.Dot, new string[2] { "Pickable_Blueberries", "Blueberries" }, new string[1] { "$item_blueberries" }, 10f, (GathererConfig cfg) => cfg.TrackBerries.Value && cfg.MarkBlueberry.Value)); Register(new ResourceDefinition("Cloudberry", "Cloudberries", "Cloudberries", ResourceCategory.Berries, PinIconType.Dot, new string[3] { "Pickable_Cloudberry", "Cloudberry", "Cloudberries" }, new string[1] { "$item_cloudberry" }, 10f, (GathererConfig cfg) => cfg.TrackBerries.Value && cfg.MarkCloudberry.Value)); Register(new ResourceDefinition("Thistle", "Thistle", "Thistle", ResourceCategory.HerbsAndPlants, PinIconType.Dot, new string[2] { "Pickable_Thistle", "Thistle" }, new string[1] { "$item_thistle" }, 10f, (GathererConfig cfg) => cfg.TrackHerbsAndPlants.Value && cfg.MarkThistle.Value)); Register(new ResourceDefinition("Dandelion", "Dandelion", "Dandelion", ResourceCategory.HerbsAndPlants, PinIconType.Dot, new string[2] { "Pickable_Dandelion", "Dandelion" }, new string[1] { "$item_dandelion" }, 10f, (GathererConfig cfg) => cfg.TrackHerbsAndPlants.Value && cfg.MarkDandelion.Value)); Register(new ResourceDefinition("CarrotSeeds", "Carrot Seeds", "Carrot Seeds", ResourceCategory.HerbsAndPlants, PinIconType.Dot, new string[2] { "Pickable_SeedCarrot", "CarrotSeeds" }, new string[1] { "$item_carrotseeds" }, 10f, (GathererConfig cfg) => cfg.TrackHerbsAndPlants.Value && cfg.MarkCarrotSeeds.Value)); Register(new ResourceDefinition("TurnipSeeds", "Turnip Seeds", "Turnip Seeds", ResourceCategory.HerbsAndPlants, PinIconType.Dot, new string[2] { "Pickable_SeedTurnip", "TurnipSeeds" }, new string[1] { "$item_turnipseeds" }, 10f, (GathererConfig cfg) => cfg.TrackHerbsAndPlants.Value && cfg.MarkTurnipSeeds.Value)); Register(new ResourceDefinition("OnionSeeds", "Onion Seeds", "Onion Seeds", ResourceCategory.HerbsAndPlants, PinIconType.Dot, new string[2] { "Pickable_SeedOnion", "OnionSeeds" }, new string[1] { "$item_onionseeds" }, 10f, (GathererConfig cfg) => cfg.TrackHerbsAndPlants.Value && cfg.MarkOnionSeeds.Value)); Register(new ResourceDefinition("Fiddlehead", "Fiddlehead", "Fiddlehead", ResourceCategory.HerbsAndPlants, PinIconType.Dot, new string[2] { "Pickable_Fiddlehead", "Fiddlehead" }, new string[1] { "$item_fiddlehead" }, 10f, (GathererConfig cfg) => cfg.TrackHerbsAndPlants.Value && cfg.MarkFiddlehead.Value)); Register(new ResourceDefinition("Barley", "Barley", "Barley", ResourceCategory.FarmingCrops, PinIconType.Dot, new string[3] { "Pickable_Barley", "Pickable_Barley_Wild", "Barley" }, new string[1] { "$item_barley" }, 10f, (GathererConfig cfg) => cfg.TrackFarmingCrops.Value && cfg.MarkBarley.Value)); Register(new ResourceDefinition("Flax", "Flax", "Flax", ResourceCategory.FarmingCrops, PinIconType.Dot, new string[3] { "Pickable_Flax", "Pickable_Flax_Wild", "Flax" }, new string[1] { "$item_flax" }, 10f, (GathererConfig cfg) => cfg.TrackFarmingCrops.Value && cfg.MarkFlax.Value)); Register(new ResourceDefinition("Copper", "Copper Deposit", "Copper", ResourceCategory.OresAndVeins, PinIconType.Hammer, new string[3] { "rock4_copper", "MineRock_Copper", "copper" }, new string[1] { "$piece_deposit_copper" }, 20f, (GathererConfig cfg) => cfg.TrackOresAndVeins.Value && cfg.MarkCopper.Value)); Register(new ResourceDefinition("Tin", "Tin Deposit", "Tin", ResourceCategory.OresAndVeins, PinIconType.Hammer, new string[2] { "mine_rock_tin", "tin" }, new string[1] { "$piece_deposit_tin" }, 15f, (GathererConfig cfg) => cfg.TrackOresAndVeins.Value && cfg.MarkTin.Value)); Register(new ResourceDefinition("Silver", "Silver Vein", "Silver", ResourceCategory.OresAndVeins, PinIconType.Hammer, new string[3] { "silvervein", "rock3_silver", "silver" }, new string[1] { "$piece_deposit_silver" }, 20f, (GathererConfig cfg) => cfg.TrackOresAndVeins.Value && cfg.MarkSilver.Value)); Register(new ResourceDefinition("IronMudpile", "Muddy Scrap Pile", "Muddy Scrap Pile", ResourceCategory.OresAndVeins, PinIconType.Hammer, new string[3] { "mudpile", "mudpile2", "iron" }, new string[1] { "$piece_mudpile" }, 15f, (GathererConfig cfg) => cfg.TrackOresAndVeins.Value && cfg.MarkIronMudpile.Value)); Register(new ResourceDefinition("Obsidian", "Obsidian Deposit", "Obsidian", ResourceCategory.OresAndVeins, PinIconType.Hammer, new string[3] { "MineRock_Obsidian", "rock4_forest_fracturing", "obsidian" }, new string[1] { "$piece_deposit_obsidian" }, 15f, (GathererConfig cfg) => cfg.TrackOresAndVeins.Value && cfg.MarkObsidian.Value)); Register(new ResourceDefinition("Flametal", "Flametal Ore", "Flametal", ResourceCategory.OresAndVeins, PinIconType.Hammer, new string[2] { "MineRock_Meteorite", "flametal" }, new string[1] { "$piece_deposit_flametal" }, 20f, (GathererConfig cfg) => cfg.TrackOresAndVeins.Value && cfg.MarkFlametal.Value)); Register(new ResourceDefinition("Beehive", "Wild Beehive", "Beehive", ResourceCategory.Beehives, PinIconType.Fireplace, new string[1] { "Beehive" }, new string[1] { "$piece_beehive" }, 15f, (GathererConfig cfg) => cfg.TrackBeehives.Value && cfg.MarkBeehive.Value)); Register(new ResourceDefinition("Vegvisir", "Vegvisir", "Vegvisir", ResourceCategory.Special, PinIconType.Dot, new string[1] { "Vegvisir" }, new string[1] { "$piece_vegvisir" }, 15f, (GathererConfig cfg) => cfg.TrackSpecial.Value && cfg.MarkVegvisir.Value)); Register(new ResourceDefinition("SurtlingSpawner", "Surtling Spawner", "Surtling Spawner", ResourceCategory.Special, PinIconType.Fireplace, new string[2] { "Spawner_FireHole", "FireFlies_FireHole" }, new string[2] { "$piece_surtlingspawner", "$piece_firehole" }, 15f, (GathererConfig cfg) => cfg.TrackSpecial.Value && cfg.MarkSurtlingSpawner.Value)); Register(new ResourceDefinition("AncientRoot", "Ancient Root", "Ancient Root", ResourceCategory.Special, PinIconType.Dot, new string[1] { "ResourceRoot" }, new string[1] { "$piece_resourceroot" }, 20f, (GathererConfig cfg) => cfg.TrackSpecial.Value && cfg.MarkAncientRoot.Value)); } public static void Register(ResourceDefinition def) { Definitions.Add(def); } public static ResourceDefinition? Match(string prefabName, string hoverName, string localizedName) { foreach (ResourceDefinition definition in Definitions) { if (definition.Matches(prefabName, hoverName, localizedName)) { return definition; } } return null; } public static bool IsEnabled(ResourceDefinition def, GathererConfig config, string? prefabName = null, string? localizedName = null) { if (!config.Enabled.Value) { return false; } if (config.IsCustomExcluded(def.Id) || config.IsCustomExcluded(def.DisplayName) || config.IsCustomExcluded(def.CleanShortName) || (!string.IsNullOrEmpty(prefabName) && config.IsCustomExcluded(prefabName)) || (!string.IsNullOrEmpty(localizedName) && config.IsCustomExcluded(localizedName))) { return false; } if (config.IsCustomIncluded(def.Id) || config.IsCustomIncluded(def.DisplayName) || config.IsCustomIncluded(def.CleanShortName) || (!string.IsNullOrEmpty(prefabName) && config.IsCustomIncluded(prefabName)) || (!string.IsNullOrEmpty(localizedName) && config.IsCustomIncluded(localizedName))) { return true; } return def.IsEnabledFunc(config); } } } namespace ValheimGatherer.Configuration { public class GathererConfig { private readonly Dictionary<string, ConfigEntry<bool>> _resourceVisibility = new Dictionary<string, ConfigEntry<bool>>(); private HashSet<string> _parsedIncludes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private HashSet<string> _parsedExcludes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public ConfigEntry<bool> Enabled { get; private set; } public ConfigEntry<KeyCode> ToggleUiKey { get; private set; } public ConfigEntry<DiscoveryMode> Mode { get; private set; } public ConfigEntry<bool> ShareOnCartographyTable { get; private set; } public ConfigEntry<bool> ShowHudNotification { get; private set; } public ConfigEntry<string> PinNamePrefix { get; private set; } public ConfigEntry<bool> CleanPinNames { get; private set; } public ConfigEntry<PinIconType> DefaultPinIcon { get; private set; } public ConfigEntry<PinIconType> OrePinIcon { get; private set; } public ConfigEntry<PinIconType> SpecialPinIcon { get; private set; } public ConfigEntry<float> HerbMergeRadius { get; private set; } public ConfigEntry<float> OreMergeRadius { get; private set; } public ConfigEntry<float> SpecialMergeRadius { get; private set; } public ConfigEntry<float> ProximityRadius { get; private set; } public ConfigEntry<float> ProximityScanInterval { get; private set; } public ConfigEntry<bool> TrackMushrooms { get; private set; } public ConfigEntry<bool> TrackBerries { get; private set; } public ConfigEntry<bool> TrackHerbsAndPlants { get; private set; } public ConfigEntry<bool> TrackFarmingCrops { get; private set; } public ConfigEntry<bool> TrackOresAndVeins { get; private set; } public ConfigEntry<bool> TrackBeehives { get; private set; } public ConfigEntry<bool> TrackSpecial { get; private set; } public ConfigEntry<bool> MarkRedMushroom { get; private set; } public ConfigEntry<bool> MarkYellowMushroom { get; private set; } public ConfigEntry<bool> MarkBlueMushroom { get; private set; } public ConfigEntry<bool> MarkMagecap { get; private set; } public ConfigEntry<bool> MarkJotunPuffs { get; private set; } public ConfigEntry<bool> MarkSmokePuff { get; private set; } public ConfigEntry<bool> MarkRaspberry { get; private set; } public ConfigEntry<bool> MarkBlueberry { get; private set; } public ConfigEntry<bool> MarkCloudberry { get; private set; } public ConfigEntry<bool> MarkThistle { get; private set; } public ConfigEntry<bool> MarkDandelion { get; private set; } public ConfigEntry<bool> MarkCarrotSeeds { get; private set; } public ConfigEntry<bool> MarkTurnipSeeds { get; private set; } public ConfigEntry<bool> MarkOnionSeeds { get; private set; } public ConfigEntry<bool> MarkFiddlehead { get; private set; } public ConfigEntry<bool> MarkBarley { get; private set; } public ConfigEntry<bool> MarkFlax { get; private set; } public ConfigEntry<bool> MarkCopper { get; private set; } public ConfigEntry<bool> MarkTin { get; private set; } public ConfigEntry<bool> MarkSilver { get; private set; } public ConfigEntry<bool> MarkIronMudpile { get; private set; } public ConfigEntry<bool> MarkObsidian { get; private set; } public ConfigEntry<bool> MarkFlametal { get; private set; } public ConfigEntry<bool> MarkBeehive { get; private set; } public ConfigEntry<bool> MarkVegvisir { get; private set; } public ConfigEntry<bool> MarkSurtlingSpawner { get; private set; } public ConfigEntry<bool> MarkAncientRoot { get; private set; } public ConfigEntry<string> CustomIncludeFilter { get; private set; } public ConfigEntry<string> CustomExcludeFilter { get; private set; } public ConfigEntry<bool> UseResourceIcons { get; private set; } public IReadOnlyDictionary<string, ConfigEntry<bool>> ResourceVisibility => _resourceVisibility; public GathererConfig(ConfigFile config) { InitConfig(config); UseResourceIcons = config.Bind<bool>("10 - Map Display", "UseResourceIcons", true, "Display item images on resource pins recognized by name. Saved/shared pins retain their standard icons."); foreach (ResourceDefinition allDefinition in ResourceRegistry.AllDefinitions) { _resourceVisibility[allDefinition.Id] = config.Bind<bool>("11 - Resource Visibility", allDefinition.Id, true, "Show " + allDefinition.DisplayName + " pins on the world map and minimap. Does not change tracking, saving or sharing."); } UpdateParsedFilters(); config.ConfigReloaded += delegate { UpdateParsedFilters(); }; } private void InitConfig(ConfigFile config) { Enabled = config.Bind<bool>("0 - General", "Enabled", true, "Master switch to enable or disable the Gatherer mod."); ToggleUiKey = config.Bind<KeyCode>("0 - General", "ToggleUiKey", (KeyCode)289, "Keyboard shortcut to open and close the interactive in-game Gatherer resource tracking menu (Default: F8)."); Mode = config.Bind<DiscoveryMode>("0 - General", "DiscoveryMode", DiscoveryMode.InteractAndProximity, "Discovery mode: InteractOnly (when picked/mined), InteractAndProximity (auto-marks nearby nodes), InteractAndHover (when looking at nodes), or All."); ShareOnCartographyTable = config.Bind<bool>("0 - General", "ShareOnCartographyTable", true, "When true, markings are saved and automatically shared to other players via the in-game Cartography Table."); ShowHudNotification = config.Bind<bool>("0 - General", "ShowHudNotification", true, "Display a notification in top-left HUD when a new resource is marked."); PinNamePrefix = config.Bind<string>("0 - General", "PinNamePrefix", "", "Optional prefix added to pin names (e.g. '[G] ' or empty for clean name)."); CleanPinNames = config.Bind<bool>("0 - General", "CleanPinNames", true, "Shortens redundant names (e.g. 'Copper' instead of 'Copper deposit', 'Tin' instead of 'Tin deposit')."); DefaultPinIcon = config.Bind<PinIconType>("1 - Pin Settings", "DefaultPinIcon", PinIconType.Dot, "Pin icon used for general resources like mushrooms, berries, and herbs (Dot, Fireplace, House, Hammer, Portal)."); OrePinIcon = config.Bind<PinIconType>("1 - Pin Settings", "OrePinIcon", PinIconType.Hammer, "Pin icon used for ore deposits and mineral veins."); SpecialPinIcon = config.Bind<PinIconType>("1 - Pin Settings", "SpecialPinIcon", PinIconType.Fireplace, "Pin icon used for special POIs like wild beehives and vegvisirs."); HerbMergeRadius = config.Bind<float>("1 - Pin Settings", "HerbMergeRadius", 10f, "Minimum distance in meters between pins of the same herb/mushroom/berry. Prevents duplicate pins within clusters."); OreMergeRadius = config.Bind<float>("1 - Pin Settings", "OreMergeRadius", 20f, "Minimum distance in meters between pins of the same ore deposit. Prevents duplicate pins for large veins."); SpecialMergeRadius = config.Bind<float>("1 - Pin Settings", "SpecialMergeRadius", 15f, "Minimum distance in meters for special POIs like beehives."); ProximityRadius = config.Bind<float>("2 - Proximity Radar", "ProximityRadius", 15f, "Radius around the player to scan for unpinned resources when using proximity discovery (WoW Gatherer radar style)."); ProximityScanInterval = config.Bind<float>("2 - Proximity Radar", "ProximityScanInterval", 2f, "Interval in seconds between proximity scans. Higher values improve performance, lower values spot nodes faster."); TrackMushrooms = config.Bind<bool>("3 - Categories", "TrackMushrooms", false, "Enable or disable tracking all mushrooms."); TrackBerries = config.Bind<bool>("3 - Categories", "TrackBerries", false, "Enable or disable tracking all berries."); TrackHerbsAndPlants = config.Bind<bool>("3 - Categories", "TrackHerbsAndPlants", false, "Enable or disable tracking wild plants and herbs."); TrackFarmingCrops = config.Bind<bool>("3 - Categories", "TrackFarmingCrops", false, "Enable or disable tracking wild and cultivated crops (e.g. barley, flax)."); TrackOresAndVeins = config.Bind<bool>("3 - Categories", "TrackOresAndVeins", false, "Enable or disable tracking all ore veins and mineral deposits."); TrackBeehives = config.Bind<bool>("3 - Categories", "TrackBeehives", false, "Enable or disable tracking wild beehives."); TrackSpecial = config.Bind<bool>("3 - Categories", "TrackSpecial", false, "Enable or disable tracking special POIs (vegvisir, surtling spawner, ancient roots)."); MarkRedMushroom = config.Bind<bool>("4 - Specific Resources - Mushrooms", "RedMushroom", false, "Mark standard red mushrooms."); MarkYellowMushroom = config.Bind<bool>("4 - Specific Resources - Mushrooms", "YellowMushroom", false, "Mark yellow mushrooms found in dungeons, caves, and crypts."); MarkBlueMushroom = config.Bind<bool>("4 - Specific Resources - Mushrooms", "BlueMushroom", false, "Mark blue mushrooms."); MarkMagecap = config.Bind<bool>("4 - Specific Resources - Mushrooms", "Magecap", false, "Mark Magecap mushrooms (Mistlands)."); MarkJotunPuffs = config.Bind<bool>("4 - Specific Resources - Mushrooms", "JotunPuffs", false, "Mark Jotun Puffs mushrooms (Mistlands)."); MarkSmokePuff = config.Bind<bool>("4 - Specific Resources - Mushrooms", "SmokePuff", false, "Mark Smoke Puff mushrooms (Ashlands)."); MarkRaspberry = config.Bind<bool>("5 - Specific Resources - Berries", "Raspberry", false, "Mark raspberry bushes (Meadows)."); MarkBlueberry = config.Bind<bool>("5 - Specific Resources - Berries", "Blueberry", false, "Mark blueberry bushes (Black Forest)."); MarkCloudberry = config.Bind<bool>("5 - Specific Resources - Berries", "Cloudberry", false, "Mark cloudberry bushes (Plains)."); MarkThistle = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "Thistle", false, "Mark glowing thistle (Black Forest / Swamp)."); MarkDandelion = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "Dandelion", false, "Mark dandelions (Meadows)."); MarkCarrotSeeds = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "CarrotSeeds", false, "Mark wild seed carrots (Black Forest)."); MarkTurnipSeeds = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "TurnipSeeds", false, "Mark wild seed turnips (Swamp)."); MarkOnionSeeds = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "OnionSeeds", false, "Mark wild seed onions (Mountain chests/wild)."); MarkFiddlehead = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "Fiddlehead", false, "Mark fiddleheads (Ashlands)."); MarkBarley = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "Barley", false, "Mark barley (Plains)."); MarkFlax = config.Bind<bool>("6 - Specific Resources - Herbs and Plants", "Flax", false, "Mark flax (Plains)."); MarkCopper = config.Bind<bool>("7 - Specific Resources - Ores", "CopperDeposit", false, "Mark copper deposits (Black Forest). Set to true if you want to track copper!"); MarkTin = config.Bind<bool>("7 - Specific Resources - Ores", "TinDeposit", false, "Mark tin deposits (Black Forest shorelines)."); MarkSilver = config.Bind<bool>("7 - Specific Resources - Ores", "SilverVein", false, "Mark silver veins (Mountains)."); MarkIronMudpile = config.Bind<bool>("7 - Specific Resources - Ores", "IronMuddyScrapPile", false, "Mark muddy scrap piles (Swamp Sunken Crypts / Swamp ground)."); MarkObsidian = config.Bind<bool>("7 - Specific Resources - Ores", "Obsidian", false, "Mark obsidian deposits (Mountains)."); MarkFlametal = config.Bind<bool>("7 - Specific Resources - Ores", "FlametalOre", false, "Mark flametal / meteorite ore deposits (Ashlands)."); MarkBeehive = config.Bind<bool>("8 - Specific Resources - Special", "Beehive", false, "Mark wild beehives found in abandoned buildings."); MarkVegvisir = config.Bind<bool>("8 - Specific Resources - Special", "Vegvisir", false, "Mark Vegvisir runestones."); MarkSurtlingSpawner = config.Bind<bool>("8 - Specific Resources - Special", "SurtlingSpawner", false, "Mark fire geysers / surtling spawners (Swamp / Ashlands)."); MarkAncientRoot = config.Bind<bool>("8 - Specific Resources - Special", "AncientRoot", false, "Mark ancient roots / sap roots (Mistlands)."); CustomIncludeFilter = config.Bind<string>("9 - Advanced Filters", "CustomIncludeFilter", "", "Comma-separated list of prefab names or localized names to always mark, e.g. 'DragonEgg, GoblinTotem'."); CustomExcludeFilter = config.Bind<string>("9 - Advanced Filters", "CustomExcludeFilter", "", "Comma-separated list of prefab names or localized names to never mark, e.g. 'Copper, Stone, Flint'."); CustomIncludeFilter.SettingChanged += delegate { UpdateParsedFilters(); }; CustomExcludeFilter.SettingChanged += delegate { UpdateParsedFilters(); }; } private void UpdateParsedFilters() { _parsedIncludes = ParseFilterString(CustomIncludeFilter?.Value); _parsedExcludes = ParseFilterString(CustomExcludeFilter?.Value); } public static HashSet<string> ParseFilterString(string? filter) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(filter)) { return hashSet; } string[] array = filter.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } return hashSet; } public bool IsCustomIncluded(string name) { if (string.IsNullOrWhiteSpace(name)) { return false; } return _parsedIncludes.Contains(name); } public bool IsCustomExcluded(string name) { if (string.IsNullOrWhiteSpace(name)) { return false; } return _parsedExcludes.Contains(name); } } public enum ResourceCategory { Mushrooms, Berries, HerbsAndPlants, FarmingCrops, OresAndVeins, Beehives, Special, Custom } public enum DiscoveryMode { InteractOnly, InteractAndProximity, InteractAndHover, All } public enum PinIconType { Dot = 3, Fireplace = 0, House = 1, Hammer = 2, Portal = 6 } }