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 HexRareScanner; 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")] [HarmonyPatch(typeof(Character), "OnDeath")] internal static class PatchCharacterOnDeath { internal static void Prefix(Character __instance) { if (Plugin.IsModEnabled && !((Object)(object)__instance == (Object)null)) { string prefabNameFromClone = PrefabNameHelper.GetPrefabNameFromClone(((Object)((Component)__instance).gameObject).name); int creatureLevel = Plugin.GetCreatureLevel(__instance); if (Plugin.IsTrackedPrefab(prefabNameFromClone, creatureLevel)) { PinManager.RemoveCreaturePin(__instance); } } } } namespace HexRareScanner { internal static class PinManager { private static readonly Dictionary PinsByZdoid = new Dictionary(); private static readonly FieldInfo CharacterNViewField = typeof(Character).GetField("m_nview", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo RemovePinMethod = typeof(Minimap).GetMethod("RemovePin", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(PinData) }, null); internal static void AddCreaturePin(Character character, Vector3 position, string pinName) { //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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null) { return; } ZDOID zdoId = GetZdoId(character); string text = (((Object)(object)character != (Object)null) ? ((Object)((Component)character).gameObject).name : "null"); if (zdoId == ZDOID.None) { Plugin.Log.LogWarning((object)("Could not get ZDOID for character " + text + ". Pin will not be added.")); } else if (!PinsByZdoid.ContainsKey(zdoId)) { PinData val = Minimap.instance.AddPin(position, (PinType)12, pinName, false, false, 0L, default(PlatformUserID)); if (val != null) { 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Minimap.instance == (Object)null)) { ZDOID zdoId = GetZdoId(character); string text = (((Object)(object)character != (Object)null) ? ((Object)((Component)character).gameObject).name : "null"); PinData value; if (zdoId == ZDOID.None) { Plugin.Log.LogWarning((object)("Could not get ZDOID for character " + text + ". Pin will not be removed.")); } else if (PinsByZdoid.TryGetValue(zdoId, out value)) { RemovePinMethod?.Invoke(Minimap.instance, new object[1] { value }); PinsByZdoid.Remove(zdoId); } else { Plugin.Log.LogWarning((object)$"No pin found for {text}. ZDOID: {zdoId}"); } } } 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(); } } [BepInPlugin("com.hex.rarescanner", "HexRareScanner", "1.0.0")] public class Plugin : BaseUnityPlugin { private 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.0.0"; private Harmony _harmonyInstance; private static ConfigEntry _isModEnabled; private static ConfigEntry _playTrackedCreatureSound; private static readonly Dictionary TrackedCreatures = new Dictionary(); internal static readonly FieldInfo CharacterMLevelField = typeof(Character).GetField("m_level", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static Plugin Instance; internal static ManualLogSource Log; internal static bool IsModEnabled => _isModEnabled?.Value ?? false; internal static bool PlayTrackedCreatureSound => _playTrackedCreatureSound?.Value ?? false; private void Awake() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; InitializeConfig(); _harmonyInstance = new Harmony("com.hex.rarescanner"); _harmonyInstance.PatchAll(); Log.LogInfo((object)"HexRareScanner v1.0.0 loaded."); } private void OnDestroy() { Log.LogInfo((object)"HexRareScanner v1.0.0 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("General", "PlayTrackedCreatureSound", true, "Enable or disable the tracked creature spawn sound."); AddTrackedCreature("Serpent", "Track Sea Serpents", "Sea Serpent", "sfx_serpent_taunt"); AddTrackedCreature("BonemawSerpent", "Track Bonemaw Serpents", "Bonemaw Serpent", "sfx_bonemaw_serpent_alert"); AddTrackedCreature("Troll", "Track Trolls", "Troll", "sfx_troll_idle"); AddTrackedCreature("Bjorn", "Track Black Forest Bears", "Black Forest Bear", "sfx_bear_bite_attack"); AddTrackedCreature("Unbjorn", "Track Vile Bears", "Vile Bear", "sfx_bear_bite_attack"); AddTrackedCreature("Abomination", "Track Abominations", "Abomination", "sfx_abomination_arise_end"); AddTrackedCreature("StoneGolem", "Track Stone Golems", "Stone Golem", "sfx_stonegolem_idle"); AddTrackedCreature("Morgen", "Track Morgens", "Morgen", "sfx_morgen_idle"); AddTrackedCreature("Wolf", "Track 2-star Wolves", "Wolf", "sfx_wolf_alerted", 3); AddTrackedCreature("Boar", "Track 2-star Boars", "Boar", "sfx_boar_idle", 3); AddTrackedCreature("Deer", "Track 2-star Deer", "Deer", "sfx_deer_idle", 3); AddTrackedCreature("Asksvin", "Track 2-star Asksvin", "Asksvin", "sfx_asksvin_idle", 3); AddTrackedCreature("FallenValkyrie", "Track Fallen Valkyrie", "FallenValkyrie", "sfx_valkyrie_flapwing"); } private void AddTrackedCreature(string prefabName, string configName, string displayName, string soundEffectName, int rarityLevel = 1) { TrackedCreatures[prefabName] = new TrackedCreatureSetting(((BaseUnityPlugin)this).Config.Bind("Tracking", configName, true, "Enable or disable tracking of " + displayName + "."), displayName, soundEffectName, rarityLevel); } internal static bool IsTrackedPrefab(string prefabName, int creatureLevel) { if (!IsModEnabled) { return false; } if (TryGetTrackedCreature(prefabName, out var trackedCreature) && trackedCreature.Enabled != null && trackedCreature.Enabled.Value) { return creatureLevel >= trackedCreature.RarityLevel; } return false; } internal static string GetDisplayName(string prefabName) { if (!TryGetTrackedCreature(prefabName, out var trackedCreature)) { return prefabName; } return trackedCreature.DisplayName; } internal static string GetSoundEffectName(string prefabName) { if (!TryGetTrackedCreature(prefabName, out var trackedCreature)) { return null; } return trackedCreature.SoundEffectName; } internal static int GetCreatureLevel(Character character) { if ((Object)(object)character == (Object)null || CharacterMLevelField == null) { return 1; } object value = CharacterMLevelField.GetValue(character); if (value is int) { return (int)value; } return 1; } private static bool TryGetTrackedCreature(string prefabName, out TrackedCreatureSetting trackedCreature) { trackedCreature = null; if (string.IsNullOrEmpty(prefabName)) { return false; } if (TrackedCreatures.TryGetValue(prefabName, out trackedCreature)) { return trackedCreature != null; } return false; } } 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; } } } namespace HexRareScanner.Patches { [HarmonyPatch(typeof(Character), "Awake")] internal static class PatchCharacterAwake { private static void Postfix(Character __instance) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(DelayedScan(__instance)); } private static void PlayTrackedCreatureSound(string soundEffectName, Vector3 position) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(soundEffectName)) { return; } if ((Object)(object)ZNetScene.instance == (Object)null) { Plugin.Log.LogWarning((object)"ZNetScene.instance is null. Could not play tracked creature sound."); return; } GameObject prefab = ZNetScene.instance.GetPrefab(soundEffectName); if ((Object)(object)prefab == (Object)null) { Plugin.Log.LogWarning((object)("Could not find sound effect prefab: " + soundEffectName)); } else { Object.Instantiate(prefab, position, Quaternion.identity); } } private static IEnumerator DelayedScan(Character character) { yield return null; yield return null; if (!Plugin.IsModEnabled || (Object)(object)character == (Object)null) { yield break; } string prefabNameFromClone = PrefabNameHelper.GetPrefabNameFromClone(((Object)((Component)character).gameObject).name); int creatureLevel = Plugin.GetCreatureLevel(character); if (Plugin.IsTrackedPrefab(prefabNameFromClone, creatureLevel) && !PinManager.HasCreaturePin(character)) { string text = Plugin.GetDisplayName(prefabNameFromClone); if (creatureLevel > 1) { text = $"{creatureLevel - 1}-star {text}"; } Vector3 position = ((Component)character).transform.position; if (Plugin.PlayTrackedCreatureSound) { PlayTrackedCreatureSound(Plugin.GetSoundEffectName(prefabNameFromClone), position); } Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "A " + text + " spawned!", 0, (Sprite)null); } PinManager.AddCreaturePin(character, position, text); } } } }