using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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 Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("HexRareScanner")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("HexRareScanner")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3c9bc10-9d16-4f15-b8bd-d4fe1c71af33")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace HexRareScanner { internal static class PinManager { private static readonly Dictionary PinsByZdoid = new Dictionary(); private static readonly FieldInfo CharacterNViewField = AccessTools.Field(typeof(Character), "m_nview"); private static readonly MethodInfo RemovePinMethod = AccessTools.Method(typeof(Minimap), "RemovePin", new Type[1] { typeof(PinData) }, (Type[])null); internal static void AddCreaturePin(Character character, Vector3 position, string pinName) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null || (Object)(object)character == (Object)null) { return; } ZDOID zdoId = GetZdoId(character); if (zdoId == ZDOID.None || PinsByZdoid.ContainsKey(zdoId)) { return; } PinData val = Minimap.instance.AddPin(position, (PinType)12, pinName, false, false, 0L, default(PlatformUserID)); if (val == null) { return; } TrackedCreatureDefinition creatureDefinition = GetCreatureDefinition(PrefabNameHelper.GetPrefabNameFromClone(((Object)((Component)character).gameObject).name)); if (creatureDefinition != null && Plugin.IsCreatureIconsEnabled) { Sprite creatureIcon = GetCreatureIcon(creatureDefinition.TrophyPrefabName); if ((Object)(object)creatureIcon != (Object)null) { SetPinIcon(val, creatureIcon); } } PinsByZdoid[zdoId] = val; } internal static void RemoveCreaturePin(Character character) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Minimap.instance == (Object)null)) { ZDOID zdoId = GetZdoId(character); if (!(zdoId == ZDOID.None) && PinsByZdoid.TryGetValue(zdoId, out var value)) { RemovePinMethod?.Invoke(Minimap.instance, new object[1] { value }); PinsByZdoid.Remove(zdoId); } } } internal static PinData GetClosestCreaturePin(Vector3 position, float radius) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) PinData result = null; float num = float.MaxValue; foreach (PinData value in PinsByZdoid.Values) { if (value != null && !((Object)(object)value.m_uiElement == (Object)null) && ((Component)value.m_uiElement).gameObject.activeInHierarchy) { float num2 = Utils.DistanceXZ(position, value.m_pos); if (!(num2 >= radius) && !(num2 >= num)) { result = value; num = num2; } } } return result; } internal static bool RemoveCreaturePin(PinData pin) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (pin == null || (Object)(object)Minimap.instance == (Object)null) { return false; } ZDOID val = ZDOID.None; foreach (KeyValuePair item in PinsByZdoid) { if (item.Value == pin) { val = item.Key; break; } } if (val == ZDOID.None) { return false; } RemovePinMethod?.Invoke(Minimap.instance, new object[1] { pin }); PinsByZdoid.Remove(val); return true; } internal static ZDOID GetZdoId(Character character) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || CharacterNViewField == null) { return ZDOID.None; } object? value = CharacterNViewField.GetValue(character); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val == (Object)null || val.GetZDO() == null) { return ZDOID.None; } return val.GetZDO().m_uid; } internal static bool HasCreaturePin(Character character) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) ZDOID zdoId = GetZdoId(character); if (zdoId == ZDOID.None) { return false; } return PinsByZdoid.ContainsKey(zdoId); } internal static void Clear() { PinsByZdoid.Clear(); } private static TrackedCreatureDefinition GetCreatureDefinition(string prefabName) { foreach (TrackedCreatureDefinition creature in TrackedCreatureDefinition.Creatures) { if (creature.PrefabName == prefabName) { return creature; } } return null; } private static Sprite GetCreatureIcon(string trophyPrefabName) { if (string.IsNullOrEmpty(trophyPrefabName) || (Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(trophyPrefabName); if ((Object)(object)itemPrefab == (Object)null) { return null; } ItemDrop component = itemPrefab.GetComponent(); if ((Object)(object)component == (Object)null || component.m_itemData == null) { return null; } return component.m_itemData.GetIcon(); } private static void SetPinIcon(PinData pin, Sprite icon) { if (pin != null && !((Object)(object)icon == (Object)null)) { pin.m_icon = icon; if ((Object)(object)pin.m_iconElement != (Object)null) { pin.m_iconElement.sprite = icon; } } } } [BepInPlugin("com.hex.rarescanner", "HexRareScanner", "1.3.1")] public class Plugin : BaseUnityPlugin { internal sealed class TrackedCreatureSetting { internal ConfigEntry Enabled { get; } internal string DisplayName { get; } internal string SoundEffectName { get; } internal int RarityLevel { get; } internal TrackedCreatureSetting(ConfigEntry enabled, string displayName, string soundEffectName, int rarityLevel) { Enabled = enabled; DisplayName = displayName; SoundEffectName = soundEffectName; RarityLevel = rarityLevel; } } private const string PluginGuid = "com.hex.rarescanner"; private const string PluginName = "HexRareScanner"; private const string PluginVersion = "1.3.1"; private Harmony _harmonyInstance; private static ConfigEntry _isModEnabled; private static ConfigEntry _playTrackedCreatureSound; private static ConfigEntry _isManualPinRemovalEnabled; private static ConfigEntry _isCreatureIconsEnabled; private static readonly Dictionary TrackedCreatures = new Dictionary(); internal static Plugin Instance; internal static ManualLogSource Log; internal static readonly FieldInfo CharacterMLevelField = AccessTools.Field(typeof(Character), "m_level"); internal static bool IsModEnabled => _isModEnabled?.Value ?? false; internal static bool PlayTrackedCreatureSound => _playTrackedCreatureSound?.Value ?? false; internal static bool IsManualPinRemovalEnabled => _isManualPinRemovalEnabled?.Value ?? true; internal static bool IsCreatureIconsEnabled => _isCreatureIconsEnabled?.Value ?? true; private void Awake() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; InitializeConfig(); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmonyInstance = new Harmony("com.hex.rarescanner"); _harmonyInstance.PatchAll(executingAssembly); Log.LogInfo((object)"HexRareScanner v1.3.1 loaded."); } private void OnDestroy() { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"HexRareScanner v1.3.1 unloaded."); } Harmony harmonyInstance = _harmonyInstance; if (harmonyInstance != null) { harmonyInstance.UnpatchSelf(); } _harmonyInstance = null; Instance = null; Log = null; } private void InitializeConfig() { TrackedCreatures.Clear(); _isModEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "IsModEnabled", true, "Enable or disable the mod."); _playTrackedCreatureSound = ((BaseUnityPlugin)this).Config.Bind("Sounds", "PlayTrackedCreatureSound", true, "Enable or disable the tracked creature spawn sound."); _isManualPinRemovalEnabled = ((BaseUnityPlugin)this).Config.Bind("Map Pins", "Enable Manual Pin Removal", true, "Allow tracked creature pins to be removed by right-clicking them on the map."); _isCreatureIconsEnabled = ((BaseUnityPlugin)this).Config.Bind("Map Pins", "Enable Creature Icons", true, "Use creature icons for tracked creature map pins. When disabled, the default Valheim blue ping icon is used. Requires a game restart."); foreach (TrackedCreatureDefinition creature in TrackedCreatureDefinition.Creatures) { AddTrackedCreature(creature.ConfigSection, creature.PrefabName, creature.ConfigName, creature.DisplayName, creature.SoundEffectName, creature.MinimumLevel); } } private void AddTrackedCreature(string configSection, string prefabName, string configName, string displayName, string soundEffectName, int rarityLevel = 1) { TrackedCreatures[prefabName] = new TrackedCreatureSetting(((BaseUnityPlugin)this).Config.Bind(configSection, configName, true, "Enable or disable tracking of " + displayName + "."), displayName, soundEffectName, rarityLevel); } internal static TrackedCreatureSetting GetTrackedCreature(string prefabName) { if (!TrackedCreatures.TryGetValue(prefabName, out var value)) { return null; } if (!value.Enabled.Value) { return null; } return value; } } internal static class PrefabNameHelper { internal static string GetPrefabNameFromClone(string objectName) { if (string.IsNullOrEmpty(objectName)) { return string.Empty; } if (objectName.EndsWith("(Clone)")) { return objectName.Substring(0, objectName.Length - "(Clone)".Length); } return objectName; } } internal sealed class TrackedCreatureDefinition { internal static class Biomes { internal const string Ocean = "Tracked Creatures - Ocean"; internal const string Meadows = "Tracked Creatures - Meadows"; internal const string BlackForest = "Tracked Creatures - Black Forest"; internal const string Swamp = "Tracked Creatures - Swamp"; internal const string Mountains = "Tracked Creatures - Mountains"; internal const string Plains = "Tracked Creatures - Plains"; internal const string Mistlands = "Tracked Creatures - Mistlands"; internal const string Ashlands = "Tracked Creatures - Ashlands"; internal const string DeepNorth = "Tracked Creatures - Deep North"; } internal static readonly List Creatures = new List { new TrackedCreatureDefinition("Tracked Creatures - Ocean", "Serpent", "Track Sea Serpents", "Sea Serpent", "sfx_serpent_taunt", "TrophySerpent"), new TrackedCreatureDefinition("Tracked Creatures - Ocean", "BonemawSerpent", "Track Bonemaw Serpents", "Bonemaw Serpent", "sfx_bonemaw_serpent_alert", "TrophyBonemawSerpent"), new TrackedCreatureDefinition("Tracked Creatures - Meadows", "Deer", "Track 2-star Deer", "Deer", "sfx_deer_idle", "TrophyDeer", 3), new TrackedCreatureDefinition("Tracked Creatures - Meadows", "Deer_White", "Track White Deer", "White Deer", "sfx_deer_idle", "TrophyDeerWhite"), new TrackedCreatureDefinition("Tracked Creatures - Meadows", "Boar", "Track 2-star Boars", "Boar", "sfx_boar_idle", "TrophyBoar", 3), new TrackedCreatureDefinition("Tracked Creatures - Black Forest", "Troll", "Track Trolls", "Troll", "sfx_troll_idle", "TrophyForestTroll"), new TrackedCreatureDefinition("Tracked Creatures - Black Forest", "Bjorn", "Track Black Forest Bears", "Black Forest Bear", "sfx_bear_bite_attack", "TrophyBjorn"), new TrackedCreatureDefinition("Tracked Creatures - Swamp", "Abomination", "Track Abominations", "Abomination", "sfx_abomination_arise_end", "TrophyAbomination"), new TrackedCreatureDefinition("Tracked Creatures - Swamp", "Writhan", "Track Writhan", "Writhan", "sfx_writhan_verse_attack", "TrophyWrithan"), new TrackedCreatureDefinition("Tracked Creatures - Mountains", "StoneGolem", "Track Stone Golems", "Stone Golem", "sfx_stonegolem_idle", "TrophySGolem"), new TrackedCreatureDefinition("Tracked Creatures - Mountains", "Wolf", "Track 2-star Wolves", "Wolf", "sfx_wolf_alerted", "TrophyWolf", 3), new TrackedCreatureDefinition("Tracked Creatures - Plains", "Unbjorn", "Track Vile Bears", "Vile Bear", "sfx_bear_bite_attack", "TrophyBjorn"), new TrackedCreatureDefinition("Tracked Creatures - Mistlands", "SeekerBrute", "Track Seeker Soldiers", "Seeker Soldier", null, "TrophySeekerBrute"), new TrackedCreatureDefinition("Tracked Creatures - Mistlands", "Seeker", "Track 1-star or higher Seekers", "Seeker", null, "TrophySeeker", 2), new TrackedCreatureDefinition("Tracked Creatures - Mistlands", "Gjall", "Track Gjalls", "Gjall", null, "TrophyGjall"), new TrackedCreatureDefinition("Tracked Creatures - Ashlands", "Asksvin", "Track 2-star Asksvin", "Asksvin", "sfx_asksvin_idle", "TrophyAsksvin", 3), new TrackedCreatureDefinition("Tracked Creatures - Ashlands", "FallenValkyrie", "Track Fallen Valkyrie", "FallenValkyrie", "sfx_fallenvalkyrie_alert", "TrophyFallenValkyrie"), new TrackedCreatureDefinition("Tracked Creatures - Ashlands", "Morgen", "Track Morgens", "Morgen", "sfx_morgen_idle", "TrophyMorgen"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "Barka", "Track Barkas", "Barka", "Enemy_Barka_Footstep", "TrophyBarka"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "ShadowPerson", "Track Shadow Persons", "Shadow Person", null), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "Skeleton_DeepNorth", "Track Deep North Skeletons", "Deep North Skeleton", null, "TrophySkeleton"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "Greydwarf_Frozen", "Track 2-star Frozen Greydwarfs", "Frozen Greydwarf", null, "TrophyGreydwarf", 3), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "TrollFrost", "Track Gammeltrolls", "Gammeltroll", "sfx_gameltroll_melee_attack", "TrophyFrostTroll"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "Moose", "Track Moose", "Moose", "sfx_moose_alert", "TrophyMoose"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "FallenWarrior", "Track Fallen Warriors", "Fallen Warrior", "sfx_fallenwarrior_attack"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "ElakingMole", "Track Eyeless One", "Eyeless One", "ElakingMole_AttackClaw", "TrophyElaking"), new TrackedCreatureDefinition("Tracked Creatures - Deep North", "Elaking", "Track Elaking", "Elaking", "ElakingMole_AttackClaw", "TrophyElaking") }; internal string ConfigSection { get; } internal string PrefabName { get; } internal string ConfigName { get; } internal string DisplayName { get; } internal string SoundEffectName { get; } internal string TrophyPrefabName { get; } internal int MinimumLevel { get; } internal TrackedCreatureDefinition(string configSection, string prefabName, string configName, string displayName, string soundEffectName, string trophyPrefabName = null, int minimumLevel = 1) { ConfigSection = configSection; PrefabName = prefabName; ConfigName = configName; DisplayName = displayName; SoundEffectName = soundEffectName; TrophyPrefabName = trophyPrefabName; MinimumLevel = minimumLevel; } } } namespace HexRareScanner.Patches { [HarmonyPatch(typeof(Character), "OnDeath")] internal static class PatchCharacterOnDeath { private static void Prefix(Character __instance) { if (!Plugin.IsModEnabled || (Object)(object)__instance == (Object)null) { return; } string prefabNameFromClone = PrefabNameHelper.GetPrefabNameFromClone(((Object)((Component)__instance).gameObject).name); if (!string.IsNullOrWhiteSpace(prefabNameFromClone)) { Plugin.TrackedCreatureSetting trackedCreature = Plugin.GetTrackedCreature(prefabNameFromClone); if (trackedCreature != null && (int)Plugin.CharacterMLevelField.GetValue(__instance) >= trackedCreature.RarityLevel) { PinManager.RemoveCreaturePin(__instance); } } } } [HarmonyPatch(typeof(Character), "Awake")] internal static class PatchCharacterAwake { private static void Postfix(Character __instance) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsModEnabled || (Object)(object)__instance == (Object)null) { return; } string prefabNameFromClone = PrefabNameHelper.GetPrefabNameFromClone(((Object)((Component)__instance).gameObject).name); if (string.IsNullOrWhiteSpace(prefabNameFromClone)) { return; } Plugin.TrackedCreatureSetting trackedCreature = Plugin.GetTrackedCreature(prefabNameFromClone); if (trackedCreature == null) { return; } int num = (int)Plugin.CharacterMLevelField.GetValue(__instance); if (num >= trackedCreature.RarityLevel && !PinManager.HasCreaturePin(__instance)) { string text = trackedCreature.DisplayName; if (num > 1) { text = $"{num - 1}-star {text}"; } Vector3 position = ((Component)__instance).transform.position; if (Plugin.PlayTrackedCreatureSound) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(PlayTrackedCreatureSound(trackedCreature.SoundEffectName, position)); } Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "A " + text + " spawned!", 0, (Sprite)null, false); } PinManager.AddCreaturePin(__instance, position, text); } } private static IEnumerator PlayTrackedCreatureSound(string soundEffectName, Vector3 position) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) yield return null; if (!string.IsNullOrEmpty(soundEffectName) && !((Object)(object)ZNetScene.instance == (Object)null)) { GameObject prefab = ZNetScene.instance.GetPrefab(soundEffectName); if (!((Object)(object)prefab == (Object)null)) { Object.Instantiate(prefab, position, Quaternion.identity); } } } } [HarmonyPatch(typeof(Minimap), "RemovePinUnderPointer")] internal static class PatchMinimapRemovePinUnderPointer { private static readonly MethodInfo ScreenToWorldPointMethod = AccessTools.Method(typeof(Minimap), "ScreenToWorldPoint", new Type[1] { typeof(Vector3) }, (Type[])null); private static readonly MethodInfo GetClosestPinMethod = AccessTools.Method(typeof(Minimap), "GetClosestPin", new Type[3] { typeof(Vector3), typeof(float), typeof(bool) }, (Type[])null); private static readonly MethodInfo HidePinTextInputMethod = AccessTools.Method(typeof(Minimap), "HidePinTextInput", new Type[1] { typeof(bool) }, (Type[])null); private static readonly PropertyInfo PinInteractRadiusProperty = AccessTools.Property(typeof(Minimap), "PinInteractRadius"); private static readonly FieldInfo NamePinField = AccessTools.Field(typeof(Minimap), "m_namePin"); private static bool Prefix(Minimap __instance) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsModEnabled || !Plugin.IsManualPinRemovalEnabled || (Object)(object)__instance == (Object)null) { return true; } if (ScreenToWorldPointMethod == null || GetClosestPinMethod == null || PinInteractRadiusProperty == null) { return true; } Vector3 val = (Vector3)ScreenToWorldPointMethod.Invoke(__instance, new object[1] { ZInput.pointerPosition }); float num = (float)PinInteractRadiusProperty.GetValue(__instance); PinData closestCreaturePin = PinManager.GetClosestCreaturePin(val, num); if (closestCreaturePin == null) { return true; } object? obj = GetClosestPinMethod.Invoke(__instance, new object[3] { val, num, true }); PinData val2 = (PinData)((obj is PinData) ? obj : null); if (val2 != null) { float num2 = Utils.DistanceXZ(val, closestCreaturePin.m_pos); if (Utils.DistanceXZ(val, val2.m_pos) < num2) { return true; } } HidePinTextInputMethod?.Invoke(__instance, new object[1] { false }); PinManager.RemoveCreaturePin(closestCreaturePin); NamePinField?.SetValue(__instance, null); return false; } } }