// Consolidated decompiled source — Wubarrk-Shadows_Of_Midgard v1.0.0 // Generated by Hexium's decompiled-source browser. Best-effort concatenation of every type in this // version's manifest — decompiler output isn't guaranteed to compile as-is. using System; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using System.Collections.Generic; using System.Linq; using System.Reflection; using BepInEx; using BepInEx.Logging; using UnityEngine; using BepInEx.Configuration; using HarmonyLib; using System.Globalization; using System.Text; using UnityEngine.EventSystems; using UnityEngine.UI; using System.IO; // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: Microsoft.CodeAnalysis.EmbeddedAttribute ---- namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: System.Runtime.CompilerServices.RefSafetyRulesAttribute ---- namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ShadowsOfMidgard ---- namespace ShadowsOfMidgard { [BepInPlugin("wubarrk.ShadowsOfMidgard", "Shadows of Midgard", "1.0.0")] public class ShadowsOfMidgard : BaseUnityPlugin { public const string ModGUID = "wubarrk.ShadowsOfMidgard"; public const string ModName = "Shadows of Midgard"; public const string ModVersion = "1.0.0"; private object _harmonyInstance; public static ManualLogSource Log; private List PatchSuccess; private List PatchFail; private static readonly Dictionary PatchDescriptions = new Dictionary { { "BaseAI_StealthBrain_Patch", "BaseAI.CanSenseTarget — stealth detection gate" }, { "BaseAI_StealthBrain_IsAlerted_Patch", "BaseAI.IsAlerted — unified alertness state" }, { "BaseAI_SetMoveDir_Patch", "Character.SetMoveDir — state-based movement control" }, { "Character_Damage_Patch", "Character.Damage — damage awareness boost" }, { "CharacterOnDamagedPatch", "Character.OnDamaged — hit reaction awareness" }, { "Humanoid_Attack_Patch", "Humanoid.StartAttack — combat authority gate" }, { "MonsterAI_StealthBrain_UpdateAI_Patch", "MonsterAI.UpdateAI — behavior execution" }, { "MonsterAI_StealthBrain_UpdateTarget_Patch", "MonsterAI.UpdateTarget — target acquisition" } }; private void Awake() { try { Log = base.Logger; Log.LogInfo("Shadows of Midgard v1.0.0 awakening from the mists of Ginnungagap..."); try { ConfigManager.Load(base.Config); } catch (Exception arg) { Log.LogWarning($"ConfigManager.Load threw an exception: {arg}"); } try { StealthUIRoot.Init(); StealthUIController.Init(); try { ConfigSync.Init(); } catch (Exception arg2) { Log.LogWarning($"ConfigSync.Init threw an exception: {arg2}"); } } catch (Exception arg3) { Log.LogWarning($"Stealth UI initialization threw an exception: {arg3}"); } PatchSuccess = new List(); PatchFail = new List(); Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetType("HarmonyLib.Harmony"); if (type != null) { break; } } catch { } } if (type == null) { Log.LogWarning("Harmony not found in loaded assemblies; skipping runtime patching."); } else { try { ConstructorInfo constructor = type.GetConstructor(new Type[1] { typeof(string) }); if (constructor != null) { _harmonyInstance = constructor.Invoke(new object[1] { "wubarrk.ShadowsOfMidgard" }); } else { Log.LogWarning("Harmony constructor(string) not found; skipping patching."); } ApplyPatchesWithSaga(); } catch (Exception arg4) { Log.LogWarning($"Failed to initialize Harmony via reflection: {arg4}"); } } Log.LogInfo("Shadows of Midgard loaded successfully. May your steps be silent."); } catch (Exception arg5) { base.Logger.LogError(string.Format("{0} failed to initialize: {1}", "Shadows of Midgard", arg5)); } } private void OnDestroy() { try { try { ConfigSync.Shutdown(); } catch (Exception arg) { Log.LogWarning($"ConfigSync.Shutdown error: {arg}"); } if (_harmonyInstance != null) { MethodInfo method = _harmonyInstance.GetType().GetMethod("UnpatchSelf", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { method.Invoke(_harmonyInstance, null); } } } catch (Exception arg2) { Log.LogWarning($"Error while unpatching Harmony: {arg2}"); } Log.LogInfo("Shadows of Midgard unpatched and returned to the halls of Valhalla."); } private void ApplyPatchesWithSaga() { if (_harmonyInstance == null) { PrintPatchSaga(); return; } PatchSuccess = new List(); PatchFail = new List(); try { Type type = _harmonyInstance.GetType(); List list = (from t in typeof(ShadowsOfMidgard).Assembly.GetTypes() where t.Namespace != null && t.Namespace.StartsWith("ShadowsOfMidgard.Patches") && HasHarmonyAttributes(t) select t).ToList(); Log.LogInfo($"Discovered {list.Count} Harmony patches. Forging..."); foreach (Type item in list) { try { ApplyPatchType(_harmonyInstance, type, item); PatchSuccess.Add(item.Name); } catch (Exception ex) { PatchFail.Add(item.Name); Log.LogWarning("✗ " + item.Name + ": " + ex.Message); for (Exception innerException = ex.InnerException; innerException != null; innerException = innerException.InnerException) { Log.LogWarning(" → " + innerException.Message); } } } } catch (Exception ex2) { Log.LogWarning($"Error while scanning patches: {ex2}"); for (Exception innerException2 = ex2.InnerException; innerException2 != null; innerException2 = innerException2.InnerException) { Log.LogWarning(" Inner: " + innerException2.Message); } } PrintPatchSaga(); } private void ApplyPatchType(object harmonyInstance, Type harmonyType, Type patchType) { MethodInfo method = harmonyType.GetMethod("CreateClassProcessor", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(Type) }, null); if (method == null) { throw new MethodAccessException("CreateClassProcessor method not found on Harmony type"); } object obj = method.Invoke(harmonyInstance, new object[1] { patchType }); if (obj == null) { throw new InvalidOperationException("CreateClassProcessor returned null for " + patchType.Name); } MethodInfo method2 = obj.GetType().GetMethod("Patch", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); if (method2 == null) { throw new MethodAccessException("Patch method not found on PatchClassProcessor"); } method2.Invoke(obj, null); } private bool HasHarmonyAttributes(Type t) { try { foreach (CustomAttributeData customAttributesDatum in t.GetCustomAttributesData()) { if (customAttributesDatum.AttributeType != null && customAttributesDatum.AttributeType.Name != null && customAttributesDatum.AttributeType.Name.IndexOf("HarmonyPatch", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } catch { } return false; } private void PrintPatchSaga() { if (ShadowsOfMidgardConfig.ShowPatchSaga != null && !ShadowsOfMidgardConfig.ShowPatchSaga.Value) { Log.LogInfo($"Patch results: {PatchSuccess.Count} succeeded, {PatchFail.Count} failed"); return; } Log.LogInfo("══════════════════════════════════════════════════"); Log.LogInfo("⚔\ufe0f Saga of the Patches — As Told by the Skalds ⚔\ufe0f"); Log.LogInfo("══════════════════════════════════════════════════"); foreach (string item in PatchSuccess) { string value; string text = (PatchDescriptions.TryGetValue(item, out value) ? value : item); Log.LogInfo(" \ud83d\udfe2 " + text); } foreach (string item2 in PatchFail) { string value2; string text2 = (PatchDescriptions.TryGetValue(item2, out value2) ? value2 : item2); Log.LogWarning(" \ud83d\udd34 " + text2); } Log.LogInfo("──────────────────────────────────────────────────"); Log.LogInfo($" Forged: {PatchSuccess.Count} | Shattered: {PatchFail.Count} | Total: {PatchSuccess.Count + PatchFail.Count}"); if (PatchFail.Count == 0) { Log.LogInfo(" All patches stand firm like the roots of Yggdrasil."); } else { Log.LogWarning(" Some patches faltered. The ravens whisper of trouble..."); Log.LogWarning(" Consult the runes (the log above) to uncover the cause."); } Log.LogInfo("══════════════════════════════════════════════════"); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ArmorProfile ---- namespace ShadowsOfMidgard { public class ArmorProfile { public ArmorWeight Weight; public ArmorMaterial Material; public bool HasCamoTag; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ArmorWeight ---- namespace ShadowsOfMidgard { public enum ArmorWeight { Light, Medium, Heavy } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ArmorMaterial ---- namespace ShadowsOfMidgard { public enum ArmorMaterial { Cloth, Leather, Metal, Bone, Bark, Magic, Unknown } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ArmorProfileSystem ---- namespace ShadowsOfMidgard { public static class ArmorProfileSystem { private static readonly Dictionary Cache = new Dictionary(); public static ArmorProfile GetProfile(ItemDrop.ItemData item) { if (item == null || item.m_shared == null) { return null; } string key = item.m_shared.m_name ?? ""; if (Cache.TryGetValue(key, out var value)) { return value; } ArmorProfile armorProfile = new ArmorProfile(); DetectMaterial(item, armorProfile); DetectWeight(item, armorProfile); DetectCamo(item, armorProfile); Cache[key] = armorProfile; return armorProfile; } private static void DetectMaterial(ItemDrop.ItemData item, ArmorProfile profile) { string text = item.m_shared.m_name.ToLowerInvariant(); if (text.Contains("iron") || text.Contains("bronze") || text.Contains("metal")) { profile.Material = ArmorMaterial.Metal; } else if (text.Contains("leather") || text.Contains("hide")) { profile.Material = ArmorMaterial.Leather; } else { profile.Material = ArmorMaterial.Cloth; } } private static void DetectWeight(ItemDrop.ItemData item, ArmorProfile profile) { float armor = item.m_shared.m_armor; if (armor <= 12f) { profile.Weight = ArmorWeight.Light; } else if (armor <= 28f) { profile.Weight = ArmorWeight.Medium; } else { profile.Weight = ArmorWeight.Heavy; } } private static void DetectCamo(ItemDrop.ItemData item, ArmorProfile profile) { string text = item.m_shared.m_name.ToLowerInvariant(); profile.HasCamoTag = text.Contains("forest") || text.Contains("swamp") || text.Contains("camo") || text.Contains("ghillie"); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ArmorUtils ---- namespace ShadowsOfMidgard { public static class ArmorUtils { public static ArmorProfile GetArmorProfile(ItemDrop.ItemData item) { return ArmorProfileSystem.GetProfile(item); } public static float GetTotalStealthPenalty(Player player) { float num = 0f; foreach (ItemDrop.ItemData equippedItem in player.GetInventory().GetEquippedItems()) { ArmorProfile armorProfile = GetArmorProfile(equippedItem); if (armorProfile != null) { switch (armorProfile.Weight) { case ArmorWeight.Light: num += 0.1f; break; case ArmorWeight.Medium: num += 0.25f; break; case ArmorWeight.Heavy: num += 0.45f; break; } } } return Mathf.Clamp01(num); } public static float GetTotalArmorNoise(Player player) { float num = 0f; foreach (ItemDrop.ItemData equippedItem in player.GetInventory().GetEquippedItems()) { ArmorProfile armorProfile = GetArmorProfile(equippedItem); if (armorProfile != null) { num = armorProfile.Material switch { ArmorMaterial.Metal => num + 0.5f, ArmorMaterial.Leather => num + 0.25f, ArmorMaterial.Cloth => num + 0.1f, _ => num + 0.2f, }; } } return Mathf.Clamp01(num); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ConfigManager ---- namespace ShadowsOfMidgard { public static class ConfigManager { public static void Load(ConfigFile file) { ShadowsOfMidgardConfig.LoadUIConfig(file); if (!(ZNet.instance != null) || ZNet.instance.IsServer()) { ShadowsOfMidgardConfig.LoadGameplayConfig(file); } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ShadowsOfMidgardConfig ---- namespace ShadowsOfMidgard { public static class ShadowsOfMidgardConfig { public static StealthConfigModel Active; public static ConfigEntry UI_EyePosX; public static ConfigEntry UI_EyePosY; public static ConfigEntry UI_NoisePosX; public static ConfigEntry UI_NoisePosY; public static ConfigEntry DebugVision; public static ConfigEntry DebugAI; public static ConfigEntry ShowPatchSaga; public static StealthConfigModel LoadGameplayConfig(ConfigFile file) { StealthConfigModel stealthConfigModel = new StealthConfigModel(); stealthConfigModel.EnableVisibilitySystem = file.Bind("Systems", "EnableVisibilitySystem", defaultValue: true, "Enable visibility calculations.").Value; stealthConfigModel.EnableNoiseSystem = file.Bind("Systems", "EnableNoiseSystem", defaultValue: true, "Enable noise calculations.").Value; stealthConfigModel.EnableHidingSystem = file.Bind("Systems", "EnableHidingSystem", defaultValue: true, "Enable hiding calculations.").Value; stealthConfigModel.EnableCamoSystem = file.Bind("Systems", "EnableCamoSystem", defaultValue: true, "Enable camouflage calculations.").Value; LoadStealthBrainConfig(file, stealthConfigModel); LoadVisibilityConfig(file, stealthConfigModel); LoadNoiseConfig(file, stealthConfigModel); LoadHidingConfig(file, stealthConfigModel); LoadCamoConfig(file, stealthConfigModel); LoadAwarenessConfig(file, stealthConfigModel); LoadDetectionRangeConfig(file, stealthConfigModel); LoadDebugConfig(file); Active = stealthConfigModel; return stealthConfigModel; } public static void LoadUIConfig(ConfigFile file) { UI_EyePosX = file.Bind("UI", "EyePosX", 80f, "Horizontal position of the stealth gem."); UI_EyePosY = file.Bind("UI", "EyePosY", 80f, "Vertical position of the stealth gem."); UI_NoisePosX = file.Bind("UI", "NoisePosX", 80f, "Horizontal position of the noise meter."); UI_NoisePosY = file.Bind("UI", "NoisePosY", 40f, "Vertical position of the noise meter."); } public static void LoadDebugConfig(ConfigFile file) { DebugVision = file.Bind("Debug", "EnableVisionDebug", defaultValue: false, "Enable detailed debug logs and overlays for stealth systems."); DebugAI = file.Bind("Debug", "EnableAIDebug", defaultValue: false, "Enable detailed AI debug logs (sensing, decisions, movement)."); ShowPatchSaga = file.Bind("Debug", "ShowPatchSaga", defaultValue: true, "Show the epic patching saga dialog at startup (patch success/fail summary)."); } private static void LoadStealthBrainConfig(ConfigFile file, StealthConfigModel cfg) { cfg.VisionThreshold = Clamp(file.Bind("StealthBrain", "VisionThreshold", 0.25f, "Minimum visibility required for AI to see the player. Higher = harder to see.").Value, 0f, 1f); cfg.HearingThreshold = Clamp(file.Bind("StealthBrain", "HearingThreshold", 0.15f, "Minimum noise required for AI to hear the player. Higher = harder to hear.").Value, 0f, 1f); cfg.SuspicionGain = Clamp(file.Bind("StealthBrain", "SuspicionGain", 2.5f, "Rate at which suspicion increases when partially detected (heard). Higher = faster detection.").Value, 0f, 5f); cfg.AlertGain = Clamp(file.Bind("StealthBrain", "AlertGain", 3f, "Rate at which alertness increases when clearly detected (seen). Higher = faster engagement.").Value, 0f, 5f); cfg.DetectionDecay = Clamp(file.Bind("StealthBrain", "DetectionDecay", 0.5f, "Rate at which detection decays when the player is not sensed. Higher = faster cool-down.").Value, 0f, 5f); cfg.SearchDuration = Clamp(file.Bind("StealthBrain", "SearchDuration", 4f, "How long AI will search after losing sight (seconds). Default: 4s.").Value, 1f, 30f); cfg.GiveUpTime = Clamp(file.Bind("StealthBrain", "GiveUpTime", 8f, "How long after losing all signals the AI gives up (seconds). Default: 8s.").Value, 1f, 60f); } private static void LoadVisibilityConfig(ConfigFile file, StealthConfigModel cfg) { cfg.LightPenalty = Clamp(file.Bind("Visibility", "LightPenalty", 0.35f, "Visibility modifier in bright light (0-1). Lower = darker overall.").Value, 0f, 2f); cfg.ShadowBonus = Clamp(file.Bind("Visibility", "ShadowBonus", 0.25f, "Visibility reduction when in shadow. Higher = harder to see in shade.").Value, 0f, 1f); cfg.GrassBonus = Clamp(file.Bind("Visibility", "GrassBonus", 0.2f, "Visibility reduction when crouching in grass/terrain.").Value, 0f, 1f); cfg.MovementPenalty = Clamp(file.Bind("Visibility", "MovementPenalty", 0.8f, "Visibility increase from movement speed. Higher = more visible when moving.").Value, 0f, 3f); cfg.ArmorVisibility = Clamp(file.Bind("Visibility", "ArmorVisibility", 0.6f, "How visible heavy armor makes the player.").Value, 0f, 3f); cfg.WeatherVisReduction = Clamp(file.Bind("Visibility", "WeatherVisReduction", 0.2f, "Visibility reduction during rain or snow. Higher = harder to see in bad weather.").Value, 0f, 1f); cfg.FogVisReduction = Clamp(file.Bind("Visibility", "FogVisReduction", 0.3f, "Visibility reduction from fog and mist. Higher = harder to see in fog.").Value, 0f, 1f); } private static void LoadNoiseConfig(ConfigFile file, StealthConfigModel cfg) { cfg.MovementNoise = Clamp(file.Bind("Noise", "MovementNoise", 0.7f, "Noise generated by movement. Higher = louder when moving.").Value, 0f, 3f); cfg.ArmorNoise = Clamp(file.Bind("Noise", "ArmorNoise", 0.6f, "Noise generated by heavy armor. Higher = louder with heavy gear.").Value, 0f, 3f); cfg.CrouchNoiseReduction = Clamp(file.Bind("Noise", "CrouchNoiseReduction", 0.6f, "Noise reduction when crouching. Higher = quieter when crouched.").Value, 0f, 1f); cfg.WeatherNoiseReduction = Clamp(file.Bind("Noise", "WeatherNoiseReduction", 0.4f, "Noise reduction during rain/snow. Higher = louder weather masks noise better.").Value, 0f, 1f); } private static void LoadHidingConfig(ConfigFile file, StealthConfigModel cfg) { cfg.BushBonus = Clamp(file.Bind("Hiding", "BushBonus", 0.4f, "Hiding bonus when inside bushes.").Value, 0f, 1f); cfg.GrassBonus_Hiding = Clamp(file.Bind("Hiding", "GrassBonus", 0.25f, "Hiding bonus when crouched in grass.").Value, 0f, 1f); cfg.CrouchBonus = Clamp(file.Bind("Hiding", "CrouchBonus", 0.15f, "Hiding bonus when crouching (without other cover).").Value, 0f, 1f); } private static void LoadCamoConfig(ConfigFile file, StealthConfigModel cfg) { cfg.BiomeCamo = Clamp(file.Bind("Camo", "BiomeCamo", 0.15f, "General camouflage bonus from matching biome.").Value, 0f, 1f); cfg.ArmorCamo = Clamp(file.Bind("Camo", "ArmorCamo", 0.1f, "Camouflage bonus from armor matching biome.").Value, 0f, 1f); } private static void LoadAwarenessConfig(ConfigFile file, StealthConfigModel cfg) { cfg.SuspiciousThreshold = Clamp(file.Bind("Awareness", "SuspiciousThreshold", 0.15f, "Detection level at which AI becomes Suspicious (starts investigating).").Value, 0f, 1f); cfg.AlertedThreshold = Clamp(file.Bind("Awareness", "AlertedThreshold", 0.4f, "Detection level at which AI becomes Alerted (pursues with caution).").Value, 0f, 1f); cfg.EngagedThreshold = Clamp(file.Bind("Awareness", "EngagedThreshold", 0.75f, "Detection level at which AI becomes Engaged (full combat mode).").Value, 0f, 1f); } private static void LoadDetectionRangeConfig(ConfigFile file, StealthConfigModel cfg) { cfg.MaxVisualRange = Clamp(file.Bind("DetectionRange", "MaxVisualRange", 40f, "Maximum range (meters) at which AI can see the player. Default: 40m.").Value, 5f, 100f); cfg.MaxHearingRange = Clamp(file.Bind("DetectionRange", "MaxHearingRange", 25f, "Maximum range (meters) at which AI can hear the player. Default: 25m.").Value, 5f, 80f); cfg.VisionConeHalfAngle = Clamp(file.Bind("DetectionRange", "VisionConeHalfAngle", 60f, "Half-angle (degrees) of AI vision cone. 60 = 120° total FOV. Hearing is omnidirectional.").Value, 15f, 180f); } private static float Clamp(float v, float min, float max) { if (float.IsNaN(v) || float.IsInfinity(v)) { return min; } return Mathf.Clamp(v, min, max); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthConfigModel ---- namespace ShadowsOfMidgard { public class StealthConfigModel { public float VisionThreshold = 0.25f; public float HearingThreshold = 0.15f; public float SuspicionGain = 2.5f; public float AlertGain = 3f; public float DetectionDecay = 0.5f; public float SearchDuration = 4f; public float GiveUpTime = 8f; public float LightPenalty = 0.35f; public float ShadowBonus = 0.25f; public float GrassBonus = 0.2f; public float MovementPenalty = 0.8f; public float ArmorVisibility = 0.6f; public float WeatherVisReduction = 0.2f; public float FogVisReduction = 0.3f; public float MovementNoise = 0.7f; public float ArmorNoise = 0.6f; public float CrouchNoiseReduction = 0.6f; public float WeatherNoiseReduction = 0.4f; public float BushBonus = 0.4f; public float GrassBonus_Hiding = 0.25f; public float CrouchBonus = 0.15f; public float BiomeCamo = 0.15f; public float ArmorCamo = 0.1f; public float SuspiciousThreshold = 0.15f; public float AlertedThreshold = 0.4f; public float EngagedThreshold = 0.75f; public float MaxVisualRange = 40f; public float MaxHearingRange = 25f; public float VisionConeHalfAngle = 60f; public bool EnableVisibilitySystem = true; public bool EnableNoiseSystem = true; public bool EnableHidingSystem = true; public bool EnableCamoSystem = true; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.AIAuthority ---- namespace ShadowsOfMidgard { public static class AIAuthority { public static bool IsAuthoritative(BaseAI ai) { if (ai == null) { return false; } ZNetView component = ai.GetComponent(); if (component == null) { if (ZNet.instance != null) { return ZNet.instance.IsServer(); } return false; } return component.IsOwner(); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.AwarenessSystem ---- namespace ShadowsOfMidgard { public static class AwarenessSystem { private static readonly Dictionary Data = new Dictionary(); private static int _cleanupCounter = 0; private const int CLEANUP_INTERVAL = 300; public static AwarenessData GetData(Character c) { if (!Data.TryGetValue(c, out var value)) { value = new AwarenessData(); Data[c] = value; } _cleanupCounter++; if (_cleanupCounter >= 300) { _cleanupCounter = 0; Cleanup(); } return value; } public static void Clear(Character c) { if (c != null) { Data.Remove(c); } } public static IEnumerable> GetAllData() { return Data; } public static bool HasLineOfSight(Character c, Player target) { if (c == null || target == null) { return false; } return RaycastUtils.HasLineOfSight(c.GetEyePoint(), target.transform.position); } public static void Cleanup() { foreach (Character item in Data.Keys.Where((Character c) => c == null || c.IsDead()).ToList()) { Data.Remove(item); } } public static void ClearAll() { Data.Clear(); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.BehaviorSystem ---- namespace ShadowsOfMidgard { public static class BehaviorSystem { private enum CombatStance { Defensive, Balanced, Aggressive } private static Action SetTargetInfoDelegate; static BehaviorSystem() { try { MethodInfo methodInfo = AccessTools.Method(typeof(BaseAI), "SetTargetInfo", new Type[1] { typeof(ZDOID) }); if (methodInfo != null) { SetTargetInfoDelegate = (Action)Delegate.CreateDelegate(typeof(Action), methodInfo); } else { ShadowsOfMidgard.Log?.LogWarning("[BehaviorSystem] Could not find SetTargetInfo method via reflection"); } } catch (Exception ex) { ShadowsOfMidgard.Log?.LogError("[BehaviorSystem] Failed to initialize SetTargetInfo delegate: " + ex.Message); } } private static void SetAITarget(BaseAI ai, ZDOID targetId) { if (ai == null || SetTargetInfoDelegate == null) { return; } try { SetTargetInfoDelegate(ai, targetId); if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { Character component = ai.GetComponent(); string text = ((component != null) ? component.name : ai.name); int frameCount = Time.frameCount; int num = ((ai != null) ? ai.GetInstanceID() : 0); ShadowsOfMidgard.Log?.LogInfo($"[BehaviorSystem] frame={frameCount} ai={num} SetAITarget: {text} -> {targetId}"); } } catch (Exception ex) { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value) { ShadowsOfMidgard.Log?.LogWarning("[BehaviorSystem] Failed to set AI target: " + ex.Message); } } } public static void CallNearbyAllies(Character caller, Vector3 position, float radius, Player target) { if (caller == null || caller.IsDead() || target == null) { return; } Collider[] array = Physics.OverlapSphere(position, radius); int num = ((array != null) ? array.Length : 0); int num2 = 0; List list = new List(); Collider[] array2 = array; for (int i = 0; i < array2.Length; i++) { Character component = array2[i].GetComponent(); if (!(component != null) || !(component != caller) || component.IsDead()) { continue; } MonsterAI component2 = component.GetComponent(); if (!(component2 != null) || component2.IsSleeping() || !AIAuthority.IsAuthoritative(component2)) { continue; } ZNetView component3 = target.GetComponent(); ZDO zDO = ((component3 != null) ? component3.GetZDO() : null); if (zDO != null) { SetAITarget(component2, zDO.m_uid); } AwarenessData data = AwarenessSystem.GetData(component); if (data != null) { float b = ShadowsOfMidgardConfig.Active?.AlertedThreshold ?? 0.4f; data.DetectionLevel = Mathf.Max(data.DetectionLevel, b); if (data.CurrentState < VanillaAlertness.Alerted) { data.CurrentState = VanillaAlertness.Alerted; } data.LastKnownPosition = target.transform.position; data.TimeSinceSeen = 0f; num2++; try { list.Add(component.name); } catch { } } } if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount = Time.frameCount; int num3 = ((caller != null) ? caller.GetInstanceID() : 0); ShadowsOfMidgard.Log?.LogInfo(string.Format("[CallNearbyAllies] frame={0} caller={1} callerId={2} target={3} radius={4} nearbyChecked={5} affected={6} names={7}", frameCount, caller?.name, num3, target?.name, radius, num, num2, string.Join(",", list))); } } public static void ResetAI(MonsterAI ai) { if (AIAuthority.IsAuthoritative(ai)) { SetAITarget(ai, ZDOID.None); } } public static void ExecuteAction(MonsterAI ai, AIBehaviorAction action, Player target, StealthDecision decision) { if (!AIAuthority.IsAuthoritative(ai)) { return; } Character component = ai.GetComponent(); if (component == null || component.IsDead()) { return; } switch (action) { case AIBehaviorAction.Idle: ResetAI(ai); break; case AIBehaviorAction.Flee: case AIBehaviorAction.StrategicRetreat: ResetAI(ai); if (target != null) { ApplyMovement(ai, component, decision.Movement); } break; case AIBehaviorAction.Pursue: if (target != null) { ZNetView component3 = target.GetComponent(); ZDO zDO2 = ((component3 != null) ? component3.GetZDO() : null); if (zDO2 != null) { SetAITarget(ai, zDO2.m_uid); } ApplyMovement(ai, component, decision.Movement); } break; case AIBehaviorAction.Attack: if (target != null && !target.IsDead()) { ZNetView component2 = target.GetComponent(); ZDO zDO = ((component2 != null) ? component2.GetZDO() : null); if (zDO != null) { SetAITarget(ai, zDO.m_uid); } ApplyMovement(ai, component, decision.Movement); } break; case AIBehaviorAction.Search: if (decision.SearchTarget.HasValue) { Vector3 value2 = decision.SearchTarget.Value; Vector3 position2 = component.transform.position; Vector3 normalized2 = (value2 - position2).normalized; MovementDirective movement2 = new MovementDirective { Direction = normalized2, Speed = decision.Movement.Speed, ShouldRun = decision.Movement.ShouldRun, Strategy = MovementStrategy.TowardLastSeen }; ApplyMovement(ai, component, movement2); } break; case AIBehaviorAction.Investigate: if (decision.SearchTarget.HasValue) { Vector3 value = decision.SearchTarget.Value; Vector3 position = component.transform.position; Vector3 normalized = (value - position).normalized; MovementDirective movement = new MovementDirective { Direction = normalized, Speed = 0.3f, ShouldRun = false, Strategy = MovementStrategy.TowardLastSeen }; ApplyMovement(ai, component, movement); } break; case AIBehaviorAction.CallForHelp: if (target != null && decision.GroupBehavior.CallForHelp) { CallNearbyAllies(component, component.transform.position, decision.GroupBehavior.AlertRadius, target); } break; case AIBehaviorAction.Patrol: break; } } public static void ApplyMovement(MonsterAI ai, Character c, MovementDirective movement) { if (movement.Direction == Vector3.zero) { c.SetMoveDir(Vector3.zero); return; } Vector3 vector = movement.Direction * movement.Speed; c.SetMoveDir(vector); if (movement.ShouldRun) { c.m_running = true; } else { c.m_running = false; } try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount = Time.frameCount; int num = ((ai != null) ? ai.GetInstanceID() : 0); ShadowsOfMidgard.Log?.LogInfo($"[BehaviorSystem] frame={frameCount} ai={num} ApplyMovement: Dir={vector} Speed={movement.Speed:F2} Run={movement.ShouldRun}"); } } catch { } } public static void ExecuteCombat(MonsterAI ai, Character c, StealthDecision decision) { if (AIAuthority.IsAuthoritative(ai) && !(c == null) && !c.IsDead() && decision.Combat.ShouldAttack) { if (decision.Combat.AggressionLevel < 0.3f) { SetCombatStance(c, CombatStance.Defensive); } else if (decision.Combat.AggressionLevel > 0.7f) { SetCombatStance(c, CombatStance.Aggressive); } else { SetCombatStance(c, CombatStance.Balanced); } if (decision.Combat.FavorDefense) { SetCombatStance(c, CombatStance.Defensive); } if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value) { ShadowsOfMidgard.Log.LogInfo($"[Combat Execute] {c.name}: Aggression={decision.Combat.AggressionLevel:F2} " + $"Defense={decision.Combat.FavorDefense} OptimalRange={decision.Combat.OptimalAttackRange:F2}m"); } } } private static void SetCombatStance(Character c, CombatStance stance) { if (!(c as Humanoid == null)) { switch (stance) { } } } public static float GetCombatCooldown(float baseCooldown, float aggressionLevel) { return baseCooldown * (2f - aggressionLevel); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.CamoSystem ---- namespace ShadowsOfMidgard { public static class CamoSystem { private static readonly HashSet CamoFriendlyBiomes = new HashSet { Heightmap.Biome.Meadows, Heightmap.Biome.BlackForest, Heightmap.Biome.Swamp, Heightmap.Biome.Plains, Heightmap.Biome.Mistlands }; private static readonly HashSet PartialCamoBiomes = new HashSet { Heightmap.Biome.Mountain }; private static readonly Dictionary> ArmorBiomeMap = new Dictionary> { { "forest", new HashSet { Heightmap.Biome.BlackForest, Heightmap.Biome.Meadows } }, { "swamp", new HashSet { Heightmap.Biome.Swamp } }, { "snow", new HashSet { Heightmap.Biome.Mountain, Heightmap.Biome.DeepNorth } }, { "plains", new HashSet { Heightmap.Biome.Plains } }, { "mist", new HashSet { Heightmap.Biome.Mistlands } } }; public static float GetCamoFactor(Player p) { StealthConfigModel active = ShadowsOfMidgardConfig.Active; if (p == null || active == null || !active.EnableCamoSystem) { return 0f; } float num = 0f; num += BiomeMatch(p, active); num += ArmorMatch(p, active); num = Mathf.Clamp01(num); if (ShadowsOfMidgardConfig.DebugVision.Value) { Debug.Log($"[Camo] {p.name}: camo={num:F2}"); } return num; } private static Heightmap.Biome GetPlayerBiome(Player p) { Heightmap heightmap = Heightmap.FindHeightmap(p.transform.position); if (heightmap != null) { return heightmap.GetBiome(p.transform.position); } return Heightmap.Biome.None; } private static float BiomeMatch(Player p, StealthConfigModel cfg) { Heightmap.Biome playerBiome = GetPlayerBiome(p); if (CamoFriendlyBiomes.Contains(playerBiome)) { return cfg.BiomeCamo; } if (PartialCamoBiomes.Contains(playerBiome)) { return cfg.BiomeCamo * 0.5f; } return 0f; } private static float ArmorMatch(Player p, StealthConfigModel cfg) { Heightmap.Biome playerBiome = GetPlayerBiome(p); float result = 0f; int num = 0; foreach (ItemDrop.ItemData equippedItem in p.GetInventory().GetEquippedItems()) { ArmorProfile profile = ArmorProfileSystem.GetProfile(equippedItem); if (profile == null || !profile.HasCamoTag) { continue; } string text = equippedItem.m_shared?.m_name?.ToLowerInvariant() ?? ""; bool flag = false; foreach (KeyValuePair> item in ArmorBiomeMap) { if (text.Contains(item.Key) && item.Value.Contains(playerBiome)) { flag = true; break; } } if (flag) { num++; } } if (num > 0) { result = cfg.ArmorCamo * Mathf.Min(1f, (float)num / 4f); } return result; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.ConfigSync ---- namespace ShadowsOfMidgard { public static class ConfigSync { private class ConfigSyncBehaviour : MonoBehaviour { private float _timer; private void Update() { try { _timer += Time.deltaTime; if (_timer >= 30f) { _timer = 0f; if (ZNet.instance != null && ZNet.instance.IsServer()) { BroadcastConfig(); } } } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSyncBehaviour.Update exception: {arg}"); } } private void OnDestroy() { } } private const string RPC_Request = "SOM_RequestConfig"; private const string RPC_Response = "SOM_ConfigResponse"; private const float ReSyncInterval = 30f; private static GameObject s_syncGO; private static readonly object s_lock = new object(); private static bool s_registered = false; public static bool IsRegistered { get { lock (s_lock) { return s_registered; } } } public static void Init() { try { lock (s_lock) { if (s_registered) { ShadowsOfMidgard.Log.LogInfo("ConfigSync: already registered; skipping."); return; } } if (ZRoutedRpc.instance == null) { ShadowsOfMidgard.Log.LogInfo("ConfigSync: ZRoutedRpc not ready; skipping config sync registration."); return; } ZRoutedRpc.instance.Register("SOM_ConfigResponse", OnConfigResponse); ZRoutedRpc.instance.Register("SOM_RequestConfig", OnRequestConfig); lock (s_lock) { s_registered = true; } if (ZNet.instance != null && ZNet.instance.IsServer()) { BroadcastConfig(); try { if (s_syncGO == null) { s_syncGO = new GameObject("SOM_ConfigSync"); UnityEngine.Object.DontDestroyOnLoad(s_syncGO); s_syncGO.hideFlags = HideFlags.HideAndDontSave; s_syncGO.AddComponent(); } } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync: failed to create periodic re-sync behaviour: {arg}"); } } else { try { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "SOM_RequestConfig", string.Empty); } catch (Exception arg2) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync: failed to invoke request RPC: {arg2}"); } } ShadowsOfMidgard.Log.LogInfo("ConfigSync initialized."); } catch (Exception arg3) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.Init exception: {arg3}"); } } private static void OnRequestConfig(long sender, string dummy) { try { if (ZNet.instance != null && ZNet.instance.IsServer()) { BroadcastConfig(); } } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.OnRequestConfig exception: {arg}"); } } internal static void BroadcastConfig() { try { if (ShadowsOfMidgardConfig.Active == null) { ShadowsOfMidgard.Log.LogWarning("ConfigSync: Active gameplay config is null; nothing to broadcast."); return; } string text = SerializeConfig(ShadowsOfMidgardConfig.Active); if (ZRoutedRpc.instance == null) { ShadowsOfMidgard.Log.LogWarning("ConfigSync: ZRoutedRpc.instance is null; cannot broadcast."); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "SOM_ConfigResponse", text); ShadowsOfMidgard.Log.LogInfo("ConfigSync: broadcasted authoritative gameplay config to peers."); } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.BroadcastConfig exception: {arg}"); } } private static void OnConfigResponse(long sender, string payload) { try { if (!(ZNet.instance != null) || !ZNet.instance.IsServer()) { if (string.IsNullOrEmpty(payload)) { ShadowsOfMidgard.Log.LogWarning("ConfigSync: received empty config payload; ignoring."); return; } if (ShadowsOfMidgardConfig.Active == null) { ShadowsOfMidgard.Log.LogWarning("ConfigSync: Active gameplay config not initialized on client; cannot apply server config."); return; } DeserializeAndApplyConfig(payload, ShadowsOfMidgardConfig.Active); ShadowsOfMidgard.Log.LogInfo("ConfigSync: applied server authoritative gameplay config (in-memory)."); } } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.OnConfigResponse exception: {arg}"); } } private static string SerializeConfig(StealthConfigModel model) { try { FieldInfo[] fields = typeof(StealthConfigModel).GetFields(BindingFlags.Instance | BindingFlags.Public); StringBuilder stringBuilder = new StringBuilder(); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { object value = fieldInfo.GetValue(model); if (value != null) { string value2 = ((!(value is float num)) ? ((!(value is double num2)) ? ((!(value is bool)) ? value.ToString() : (((bool)value) ? "1" : "0")) : num2.ToString(CultureInfo.InvariantCulture)) : num.ToString(CultureInfo.InvariantCulture)); stringBuilder.Append(fieldInfo.Name).Append("=").Append(value2) .Append(";"); } } return stringBuilder.ToString(); } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.SerializeConfig exception: {arg}"); return string.Empty; } } private static void DeserializeAndApplyConfig(string payload, StealthConfigModel target) { try { FieldInfo[] fields = typeof(StealthConfigModel).GetFields(BindingFlags.Instance | BindingFlags.Public); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { dictionary[fieldInfo.Name] = fieldInfo; } string[] array2 = payload.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array2.Length; i++) { string[] array3 = array2[i].Split(new char[1] { '=' }, 2); if (array3.Length != 2) { continue; } string key = array3[0]; string text = array3[1]; if (!dictionary.TryGetValue(key, out var value)) { continue; } int result3; if (value.FieldType == typeof(float)) { if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { value.SetValue(target, result); } } else if (value.FieldType == typeof(double)) { if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { value.SetValue(target, result2); } } else if (value.FieldType == typeof(bool)) { if (text == "1" || text.Equals("true", StringComparison.OrdinalIgnoreCase)) { value.SetValue(target, true); } else { value.SetValue(target, false); } } else if (value.FieldType == typeof(int) && int.TryParse(text, out result3)) { value.SetValue(target, result3); } } } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.DeserializeAndApplyConfig exception: {arg}"); } } public static void Shutdown() { try { lock (s_lock) { if (!s_registered) { return; } s_registered = false; } try { if (s_syncGO != null) { UnityEngine.Object.Destroy(s_syncGO); s_syncGO = null; } } catch (Exception arg) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.Shutdown: failed to destroy sync GameObject: {arg}"); } ShadowsOfMidgard.Log.LogInfo("ConfigSync: shutdown complete."); } catch (Exception arg2) { ShadowsOfMidgard.Log.LogWarning($"ConfigSync.Shutdown exception: {arg2}"); } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.HidingSystem ---- namespace ShadowsOfMidgard { public static class HidingSystem { private static int _vegetationMask = -1; private static int VegetationMask { get { if (_vegetationMask == -1) { _vegetationMask = LayerMask.GetMask("Default", "piece", "piece_nonsolid", "static_solid"); } return _vegetationMask; } } public static float GetHidingFactor(Player p) { StealthConfigModel active = ShadowsOfMidgardConfig.Active; if (p == null || active == null || !active.EnableHidingSystem) { return 0f; } float num = 0f; num += BushBonus(p, active); num += GrassBonus(p, active); num += CrouchBonus(p, active); num = Mathf.Clamp01(num); if (ShadowsOfMidgardConfig.DebugVision.Value) { Debug.Log($"[Hiding] {p.name}: hiding={num:F2}"); } return num; } private static float BushBonus(Player p, StealthConfigModel cfg) { Collider[] array = Physics.OverlapSphere(p.transform.position, 1f, VegetationMask); for (int i = 0; i < array.Length; i++) { if (array[i].name.ToLowerInvariant().Contains("bush")) { return cfg.BushBonus; } } return 0f; } private static float GrassBonus(Player p, StealthConfigModel cfg) { Collider[] array = Physics.OverlapSphere(p.transform.position, 1f, VegetationMask); for (int i = 0; i < array.Length; i++) { if (array[i].name.ToLowerInvariant().Contains("grass")) { return cfg.GrassBonus_Hiding; } } return 0f; } private static float CrouchBonus(Player p, StealthConfigModel cfg) { if (!p.IsCrouching()) { return 0f; } return cfg.CrouchBonus; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.NoiseSystem ---- namespace ShadowsOfMidgard { public static class NoiseSystem { public static float GetNoise(Player p) { StealthConfigModel active = ShadowsOfMidgardConfig.Active; if (p == null || active == null || !active.EnableNoiseSystem) { return 0f; } float num = 0f; num += MovementNoise(p, active); num += ArmorNoise(p, active); num -= CrouchReduction(p, active); num -= WeatherReduction(active); num = Mathf.Clamp01(num); if (ShadowsOfMidgardConfig.DebugVision.Value) { Debug.Log($"[Noise] {p.name}: noise={num:F2}"); } return num; } private static float MovementNoise(Player p, StealthConfigModel cfg) { return Mathf.Clamp01(p.GetVelocity().magnitude / 7f) * cfg.MovementNoise; } private static float ArmorNoise(Player p, StealthConfigModel cfg) { return ArmorUtils.GetTotalArmorNoise(p) * cfg.ArmorNoise; } private static float CrouchReduction(Player p, StealthConfigModel cfg) { if (!p.IsCrouching()) { return 0f; } return cfg.CrouchNoiseReduction; } private static float WeatherReduction(StealthConfigModel cfg) { EnvMan instance = EnvMan.instance; if (instance == null) { return 0f; } string text = instance.GetCurrentEnvironment()?.m_name?.ToLowerInvariant() ?? ""; if (text.Contains("rain") || text.Contains("snow") || text.Contains("mist")) { return cfg.WeatherNoiseReduction; } return 0f; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthBrain ---- namespace ShadowsOfMidgard { public static class StealthBrain { private struct CachedEvaluation { public int LastFullEvalFrame; public int LastCacheFrame; public StealthDecision Decision; } private struct StableAIKey(ulong networkUid, int instanceId) { public readonly ulong NetworkUid = networkUid; public readonly int InstanceId = instanceId; public override bool Equals(object obj) { if (obj is StableAIKey stableAIKey) { if (NetworkUid == stableAIKey.NetworkUid) { return InstanceId == stableAIKey.InstanceId; } return false; } return false; } public override int GetHashCode() { ulong networkUid = NetworkUid; return (networkUid.GetHashCode() * 397) ^ InstanceId; } public override string ToString() { if (NetworkUid == 0L) { return $"inst:{InstanceId}"; } return $"net:{NetworkUid}"; } } private const float NEARBY_ALLY_RADIUS = 30f; private const float STRATEGIC_RETREAT_DISTANCE = 50f; private const float HEALTH_CRITICAL_THRESHOLD = 0.25f; private const float HEALTH_LOW_THRESHOLD = 0.5f; private const float STRATEGIC_RETREAT_COOLDOWN = 10f; private const float ALLY_CALL_COOLDOWN = 3f; private const int EVALUATION_SKIP = 5; private static readonly Dictionary _evalCache = new Dictionary(); private static readonly Dictionary _pendingNetworkMigrations = new Dictionary(); private static int _cacheCleanupFrame = 0; private const int CACHE_CLEANUP_INTERVAL = 600; public static StealthDecision Evaluate(MonsterAI ai, Player p, float dt) { StealthDecision stealthDecision = new StealthDecision(); StealthConfigModel active = ShadowsOfMidgardConfig.Active; if (ai == null || p == null || active == null) { return stealthDecision; } Character component = ai.GetComponent(); if (component == null || component.IsDead()) { return stealthDecision; } if (ai.IsSleeping()) { return stealthDecision; } AwarenessData data = AwarenessSystem.GetData(component); if (data == null) { return stealthDecision; } StableAIKey stableAIKey = GetStableAIKey(ai); int instanceID = ai.GetInstanceID(); int frameCount = Time.frameCount; if (frameCount - _cacheCleanupFrame > 600) { _cacheCleanupFrame = frameCount; CleanupStaleCache(frameCount); } if (_evalCache.TryGetValue(stableAIKey, out var value) && value.LastFullEvalFrame == frameCount) { try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { ShadowsOfMidgard.Log?.LogInfo($"[CACHE-HIT] frame={frameCount} ai={stableAIKey} inst={instanceID} CanSense={value.Decision.CanSense} Det={value.Decision.DetectionLevel:F2}"); foreach (StableAIKey key2 in _evalCache.Keys) { if (key2.InstanceId == stableAIKey.InstanceId && !key2.Equals(stableAIKey)) { ShadowsOfMidgard.Log?.LogWarning($"[CACHE-ANOMALY] frame={frameCount} inst={instanceID} duplicate-keys: current={stableAIKey} other={key2}"); } } } } catch { } return value.Decision; } try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { string text = ((component != null) ? component.name : ((ai != null) ? ai.name : "")); string text2 = ((p != null) ? p.name : ""); ShadowsOfMidgard.Log?.LogInfo($"[StealthBrain] frame={frameCount} ai={instanceID} START: {text} -> {text2} State={data.CurrentState} Det={data.DetectionLevel:F2}"); } } catch { } EvaluateSensing(ai, p, component, data, active, dt, stealthDecision); EvaluateStateTransitions(data, active, stealthDecision, dt); AssessHealth(ai, p, data, active); EvaluateFleeing(ai, p, component, data, active, stealthDecision, dt); EvaluateNearbyAllies(ai, component, data, active); EvaluateMovement(ai, p, component, data, active, stealthDecision, dt); EvaluateCombat(ai, p, component, data, active, stealthDecision); EvaluateGroupBehavior(ai, p, component, data, active, stealthDecision, dt); DetermineAction(ai, p, component, data, active, stealthDecision); FinalizeDecision(data, stealthDecision); stealthDecision.FrameCounter = frameCount; try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { ShadowsOfMidgard.Log?.LogInfo($"[StealthBrain] frame={frameCount} ai={stableAIKey} inst={instanceID} CanSense={stealthDecision.CanSense} State={stealthDecision.State} " + $"DetLevel={stealthDecision.DetectionLevel:F2} Action={stealthDecision.RecommendedAction} " + "Reason=" + stealthDecision.DecisionReason); } } catch { } _evalCache[stableAIKey] = new CachedEvaluation { LastFullEvalFrame = frameCount, LastCacheFrame = frameCount, Decision = stealthDecision }; try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { ShadowsOfMidgard.Log?.LogInfo($"[CACHE-WRITE] frame={frameCount} ai={stableAIKey} inst={instanceID} CanSense={stealthDecision.CanSense} Det={stealthDecision.DetectionLevel:F2}"); foreach (KeyValuePair item in _evalCache) { StableAIKey key = item.Key; CachedEvaluation value2 = item.Value; if (key.InstanceId == stableAIKey.InstanceId && !key.Equals(stableAIKey) && value2.LastFullEvalFrame == frameCount) { ShadowsOfMidgard.Log?.LogWarning($"[CACHE-ANOMALY] frame={frameCount} inst={instanceID} conflicting-cache-entry: new={stableAIKey} existing={key}"); } } } } catch { } if (stableAIKey.NetworkUid == 0L) { int instanceID2 = ai.GetInstanceID(); if (!_pendingNetworkMigrations.ContainsKey(instanceID2)) { _pendingNetworkMigrations[instanceID2] = ai; try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { ShadowsOfMidgard.Log?.LogInfo($"[CACHE-PENDING] frame={frameCount} inst={instanceID2} registered for migration (no network UID yet)"); } } catch { } } } TryProcessPendingNetworkMigrations(frameCount); return stealthDecision; } private static void TryProcessPendingNetworkMigrations(int currentFrame) { if (_pendingNetworkMigrations.Count == 0 || currentFrame % 30 != 0) { return; } try { bool flag = ZNet.instance != null; bool flag2 = false; try { flag2 = ZRoutedRpc.instance != null; } catch { flag2 = false; } if (!flag || !flag2) { try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { ShadowsOfMidgard.Log?.LogInfo($"[CACHE-MIGRATE] frame={currentFrame} network not ready: ZNet={ZNet.instance != null} ZRouted={ZRoutedRpc.instance != null} pending={_pendingNetworkMigrations.Count}"); } return; } catch { return; } } } catch { } List list = new List(); foreach (KeyValuePair pendingNetworkMigration in _pendingNetworkMigrations) { int key = pendingNetworkMigration.Key; MonsterAI value = pendingNetworkMigration.Value; if (value == null) { list.Add(key); continue; } ulong num = 0uL; try { ZNetView component = value.GetComponent(); if (component != null) { ZDO zDO = component.GetZDO(); if (zDO != null) { num = (ulong)zDO.m_uid.UserID; } } } catch { } if (num == 0L) { continue; } StableAIKey key2 = new StableAIKey(0uL, key); StableAIKey key3 = new StableAIKey(num, key); if (_evalCache.TryGetValue(key2, out var value2)) { _evalCache.Remove(key2); _evalCache[key3] = value2; try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { ShadowsOfMidgard.Log?.LogInfo($"[CACHE-MIGRATE] frame={currentFrame} inst={key} -> net={num}"); } } catch { } } list.Add(key); } foreach (int item in list) { _pendingNetworkMigrations.Remove(item); } } private static StableAIKey GetStableAIKey(MonsterAI ai) { if (ai == null) { return new StableAIKey(0uL, 0); } int instanceID = ai.GetInstanceID(); try { ZNetView component = ai.GetComponent(); if (component != null) { ZDO zDO = component.GetZDO(); if (zDO != null) { return new StableAIKey((ulong)zDO.m_uid.UserID, instanceID); } } } catch { } return new StableAIKey(0uL, instanceID); } private static void EvaluateSensing(MonsterAI ai, Player p, Character c, AwarenessData data, StealthConfigModel cfg, float dt, StealthDecision decision) { float num = (cfg.EnableVisibilitySystem ? VisibilitySystem.GetVisibility(p) : 0f); float num2 = (cfg.EnableNoiseSystem ? NoiseSystem.GetNoise(p) : 0f); float num3 = (cfg.EnableHidingSystem ? HidingSystem.GetHidingFactor(p) : 0f); float num4 = (cfg.EnableCamoSystem ? CamoSystem.GetCamoFactor(p) : 0f); float num5 = num * (1f - num3) * (1f - num4); bool flag = AwarenessSystem.HasLineOfSight(c, p); float num6 = Vector3.Distance(c.transform.position, p.transform.position); float num7 = 1f - Mathf.Clamp01(num6 / cfg.MaxVisualRange); float num8 = 1f - Mathf.Clamp01(num6 / cfg.MaxHearingRange); num5 *= num7; float num9 = num2 * num8; Vector3 normalized = (p.transform.position - c.transform.position).normalized; float num10 = Vector3.Dot(c.transform.forward, normalized); float num11 = Mathf.Cos(cfg.VisionConeHalfAngle * (MathF.PI / 180f)); if (num10 < num11) { float num12 = Mathf.Clamp01((num10 - -1f) / (num11 - -1f)) * 0.1f; num5 *= num12; } bool flag2 = data.CurrentState >= VanillaAlertness.Alerted; decision.CanSee = (flag || flag2) && num5 > cfg.VisionThreshold; decision.CanHear = num9 > cfg.HearingThreshold; decision.CanSense = decision.CanSee || decision.CanHear; if (decision.CanSee) { decision.SensingConfidence = Mathf.Clamp01(num5 / (cfg.VisionThreshold * 2f)); } else if (decision.CanHear) { decision.SensingConfidence = Mathf.Clamp01(num9 / (cfg.HearingThreshold * 2f)); } else { decision.SensingConfidence = 0f; } float num13 = 0f; num13 = (decision.CanSee ? cfg.AlertGain : ((!decision.CanHear) ? (0f - cfg.DetectionDecay) : cfg.SuspicionGain)); data.DetectionLevel = Mathf.Clamp01(data.DetectionLevel + num13 * dt); if (decision.CanSee || decision.CanHear) { data.LastKnownPosition = p.transform.position; data.TimeSinceSeen = 0f; } else { data.TimeSinceSeen += dt; } decision.DetectionLevel = data.DetectionLevel; decision.Debug.Visibility = num; decision.Debug.Noise = num2; decision.Debug.Hiding = num3; decision.Debug.Camo = num4; decision.Debug.CanSee = decision.CanSee; decision.Debug.CanHear = decision.CanHear; decision.Debug.CanSense = decision.CanSense; try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount = Time.frameCount; int num14 = ((ai != null) ? ai.GetInstanceID() : 0); StableAIKey stableAIKey = GetStableAIKey(ai); string text = ((c != null) ? c.name : ((ai != null) ? ai.name : "")); ShadowsOfMidgard.Log?.LogInfo($"[StealthSense] frame={frameCount} ai={stableAIKey} inst={num14} {text}: vis={num:F2} adjVis={num5:F2} noise={num2:F2} adjNoise={num9:F2} " + $"hiding={num3:F2} camo={num4:F2} dist={num6:F1} dot={num10:F2} canSee={decision.CanSee} " + $"canHear={decision.CanHear} canSense={decision.CanSense} det={data.DetectionLevel:F2}"); } } catch { } } private static void EvaluateStateTransitions(AwarenessData data, StealthConfigModel cfg, StealthDecision decision, float dt) { VanillaAlertness currentState = data.CurrentState; float detectionLevel = data.DetectionLevel; if (detectionLevel >= cfg.EngagedThreshold) { data.CurrentState = VanillaAlertness.Engaged; } else if (detectionLevel >= cfg.AlertedThreshold) { if (data.CurrentState < VanillaAlertness.Alerted) { data.CurrentState = VanillaAlertness.Alerted; } } else if (detectionLevel >= cfg.SuspiciousThreshold && data.CurrentState < VanillaAlertness.Suspicious) { data.CurrentState = VanillaAlertness.Suspicious; } if (data.CurrentState == VanillaAlertness.Engaged && detectionLevel < cfg.AlertedThreshold * 0.7f) { data.CurrentState = VanillaAlertness.Alerted; } if (data.CurrentState == VanillaAlertness.Alerted && detectionLevel < cfg.SuspiciousThreshold * 0.5f) { data.CurrentState = VanillaAlertness.Suspicious; } if (data.CurrentState == VanillaAlertness.Suspicious && detectionLevel < 0.02f) { data.CurrentState = VanillaAlertness.Unaware; } if (data.CurrentState != currentState) { data.TimeInCurrentState = 0f; } else { data.TimeInCurrentState += dt; } decision.State = data.CurrentState; decision.Debug.State = data.CurrentState; decision.Debug.DetectionLevel = data.DetectionLevel; } private static void AssessHealth(MonsterAI ai, Player p, AwarenessData data, StealthConfigModel cfg) { Character component = ai.GetComponent(); if (!(component == null)) { data.SelfHealthPercent = component.GetHealth() / component.GetMaxHealth(); data.PlayerHealthPercent = p.GetHealth() / p.GetMaxHealth(); data.TargetDistance = Vector3.Distance(component.transform.position, p.transform.position); } } private static void EvaluateFleeing(MonsterAI ai, Player p, Character c, AwarenessData data, StealthConfigModel cfg, StealthDecision decision, float dt) { decision.Flee = default(FleeDirective); if (data.SelfHealthPercent < 0.25f && data.CurrentState >= VanillaAlertness.Alerted) { decision.Flee.ShouldFlee = true; decision.Flee.Reason = FleeReason.HealthCritical; decision.Flee.HealthThreshold = 0.25f; decision.Flee.IsStrategicRetreat = true; decision.Flee.ReturnWaitTime = 15f; decision.FleeConfidence = 0.95f; data.CurrentFleeReason = FleeReason.HealthCritical; data.FleeStartHealth = data.SelfHealthPercent; } else if (data.SelfHealthPercent < 0.5f && data.CurrentState >= VanillaAlertness.Alerted) { float t = data.SelfHealthPercent / 0.5f; float num = Mathf.Lerp(0.7f, 0f, t); if (num > UnityEngine.Random.value) { decision.Flee.ShouldFlee = true; decision.Flee.Reason = FleeReason.StrategicRetreat; decision.Flee.HealthThreshold = 0.5f; decision.Flee.IsStrategicRetreat = true; decision.Flee.ReturnWaitTime = 8f; decision.FleeConfidence = num; data.CurrentFleeReason = FleeReason.StrategicRetreat; data.FleeStartHealth = data.SelfHealthPercent; } else { decision.Flee.ShouldFlee = false; data.CurrentFleeReason = FleeReason.None; } } else { decision.Flee.ShouldFlee = false; data.CurrentFleeReason = FleeReason.None; } if (decision.Flee.ShouldFlee) { Vector3 normalized = (c.transform.position - p.transform.position).normalized; decision.Flee.FleeTarget = c.transform.position + normalized * 50f; data.FleeDirection = normalized; data.TimeSpentFleeing += dt; } else { data.TimeSpentFleeing = 0f; } decision.Debug.FleeReason = data.CurrentFleeReason; decision.Debug.FleeThreshold = decision.Flee.HealthThreshold; decision.Debug.IsStrategicRetreat = decision.Flee.IsStrategicRetreat; decision.Debug.SelfHealthPercent = data.SelfHealthPercent; decision.Debug.PlayerHealthPercent = data.PlayerHealthPercent; } private static void EvaluateNearbyAllies(MonsterAI ai, Character c, AwarenessData data, StealthConfigModel cfg) { Collider[] array = Physics.OverlapSphere(c.transform.position, 30f); int num = 0; Collider[] array2 = array; for (int i = 0; i < array2.Length; i++) { Character component = array2[i].GetComponent(); if (component != null && component != c && !component.IsDead() && component.GetComponent() != null) { MonsterAI component2 = component.GetComponent(); if (component2 != null && !component2.IsSleeping()) { num++; } } } data.NearbyAlliesCount = num; data.IsPartOfGroup = num > 0; } private static void EvaluateMovement(MonsterAI ai, Player p, Character c, AwarenessData data, StealthConfigModel cfg, StealthDecision decision, float dt) { decision.Movement = default(MovementDirective); Vector3 position = c.transform.position; Vector3 position2 = p.transform.position; Vector3 normalized = (position2 - position).normalized; float num = Vector3.Distance(position, position2); if (decision.Flee.ShouldFlee) { decision.Movement.Direction = data.FleeDirection; decision.Movement.Speed = 1f; decision.Movement.ShouldRun = true; decision.Movement.Strategy = MovementStrategy.AwayFromPlayer; data.DesiredDirection = data.FleeDirection; data.DesiredSpeed = 1f; data.ShouldRun = true; } else { switch (data.CurrentState) { case VanillaAlertness.Unaware: decision.Movement.Direction = Vector3.zero; decision.Movement.Speed = 0f; decision.Movement.ShouldRun = false; decision.Movement.Strategy = MovementStrategy.None; break; case VanillaAlertness.Suspicious: if (data.TimeSinceSeen < cfg.SearchDuration && data.LastKnownPosition != Vector3.zero) { Vector3 normalized2 = (data.LastKnownPosition - position).normalized; decision.Movement.Direction = normalized2; decision.Movement.Speed = 0.5f; decision.Movement.ShouldRun = false; decision.Movement.Strategy = MovementStrategy.TowardLastSeen; decision.SearchTarget = data.LastKnownPosition; decision.SearchRadius = 10f; } else { decision.Movement.Direction = Vector3.zero; decision.Movement.Speed = 0f; decision.Movement.ShouldRun = false; decision.Movement.Strategy = MovementStrategy.None; } break; case VanillaAlertness.Alerted: if (decision.CanSense) { decision.Movement.Direction = normalized; decision.Movement.Speed = 1f; decision.Movement.ShouldRun = true; decision.Movement.Strategy = MovementStrategy.TowardPlayer; } else if (data.TimeSinceSeen < cfg.SearchDuration && data.LastKnownPosition != Vector3.zero) { Vector3 normalized3 = (data.LastKnownPosition - position).normalized; decision.Movement.Direction = normalized3; decision.Movement.Speed = 1f; decision.Movement.ShouldRun = true; decision.Movement.Strategy = MovementStrategy.TowardLastSeen; decision.SearchTarget = data.LastKnownPosition; decision.SearchRadius = 15f; } else { decision.Movement.Direction = Vector3.zero; decision.Movement.Speed = 0f; decision.Movement.ShouldRun = false; decision.Movement.Strategy = MovementStrategy.None; } break; case VanillaAlertness.Engaged: if (num < 5f) { Vector3 flankingDirection = GetFlankingDirection(position, position2); decision.Movement.Direction = flankingDirection; decision.Movement.Speed = 0.7f; decision.Movement.ShouldRun = true; decision.Movement.Strategy = MovementStrategy.Flanking; } else { decision.Movement.Direction = normalized; decision.Movement.Speed = 1f; decision.Movement.ShouldRun = true; decision.Movement.Strategy = MovementStrategy.TowardPlayer; } break; } data.DesiredDirection = decision.Movement.Direction; data.DesiredSpeed = decision.Movement.Speed; data.ShouldRun = decision.Movement.ShouldRun; } decision.ShouldSearch = (data.CurrentState == VanillaAlertness.Suspicious || data.CurrentState == VanillaAlertness.Alerted) && data.TimeSinceSeen < cfg.SearchDuration && data.LastKnownPosition != Vector3.zero; decision.ShouldGiveUp = data.TimeSinceSeen > cfg.GiveUpTime; decision.Debug.SuggestedDirection = decision.Movement.Direction; decision.Debug.SuggestedSpeed = decision.Movement.Speed; decision.Debug.Strategy = decision.Movement.Strategy; } private static void EvaluateCombat(MonsterAI ai, Player p, Character c, AwarenessData data, StealthConfigModel cfg, StealthDecision decision) { decision.Combat = default(CombatDirective); if (data.CurrentState == VanillaAlertness.Engaged && !decision.Flee.ShouldFlee) { decision.Combat.ShouldAttack = true; decision.Combat.OptimalAttackRange = 2.5f; decision.Combat.ShouldKeepDistance = false; decision.Combat.AggressionLevel = 1f; decision.Combat.FavorDefense = data.SelfHealthPercent < 0.5f; decision.Combat.DamageThreshold = 0.25f; data.TargetPriority = TargetPriority.Immediate; decision.ShouldPursue = true; } else if (data.CurrentState == VanillaAlertness.Alerted && !decision.Flee.ShouldFlee) { decision.Combat.ShouldAttack = true; decision.Combat.OptimalAttackRange = 3f; decision.Combat.ShouldKeepDistance = true; decision.Combat.AggressionLevel = 0.7f; decision.Combat.FavorDefense = true; decision.Combat.DamageThreshold = 0.5f; data.TargetPriority = TargetPriority.Confirmed; decision.ShouldPursue = true; } else if (data.CurrentState == VanillaAlertness.Suspicious) { decision.Combat.ShouldAttack = false; decision.Combat.AggressionLevel = 0.3f; decision.Combat.FavorDefense = false; data.TargetPriority = TargetPriority.Secondary; decision.ShouldPursue = false; } else { decision.Combat.ShouldAttack = false; decision.Combat.AggressionLevel = 0f; decision.Combat.FavorDefense = false; data.TargetPriority = TargetPriority.None; decision.ShouldPursue = false; } decision.TargetPriority = data.TargetPriority; decision.Debug.AggressionLevel = decision.Combat.AggressionLevel; } private static void EvaluateGroupBehavior(MonsterAI ai, Player p, Character c, AwarenessData data, StealthConfigModel cfg, StealthDecision decision, float dt) { decision.GroupBehavior = default(GroupBehavior); if (data.CurrentState == VanillaAlertness.Engaged && data.IsPartOfGroup) { data.TimeUntilNextAllyCall -= dt; if (data.TimeUntilNextAllyCall <= 0f) { decision.GroupBehavior.CallForHelp = true; decision.GroupBehavior.AlertRadius = 30f; decision.GroupBehavior.CoordinatedAttack = data.NearbyAlliesCount >= 2; decision.GroupBehavior.MinAlliesForGroupAction = 2; data.TimeUntilNextAllyCall = 3f; } } else if (data.CurrentState == VanillaAlertness.Alerted && data.IsPartOfGroup) { data.TimeUntilNextAllyCall -= dt; if (data.TimeUntilNextAllyCall <= 0f && data.NearbyAlliesCount >= 1) { decision.GroupBehavior.CallForHelp = true; decision.GroupBehavior.AlertRadius = 30f; decision.GroupBehavior.CoordinatedAttack = false; decision.GroupBehavior.MinAlliesForGroupAction = 1; data.TimeUntilNextAllyCall = 4.5f; } } else { decision.GroupBehavior.CallForHelp = false; } decision.Debug.NearbyAlliesCount = data.NearbyAlliesCount; decision.Debug.ShouldCallForHelp = decision.GroupBehavior.CallForHelp; decision.Debug.AlertRadius = decision.GroupBehavior.AlertRadius; } private static void DetermineAction(MonsterAI ai, Player p, Character c, AwarenessData data, StealthConfigModel cfg, StealthDecision decision) { if (decision.Flee.ShouldFlee) { data.CurrentAction = (decision.Flee.IsStrategicRetreat ? AIBehaviorAction.StrategicRetreat : AIBehaviorAction.Flee); } else if (decision.ShouldGiveUp) { data.CurrentAction = AIBehaviorAction.Idle; } else if (data.CurrentState == VanillaAlertness.Unaware) { data.CurrentAction = AIBehaviorAction.Idle; } else if (data.CurrentState == VanillaAlertness.Suspicious) { data.CurrentAction = (decision.ShouldSearch ? AIBehaviorAction.Search : AIBehaviorAction.Investigate); } else if (data.CurrentState == VanillaAlertness.Alerted) { data.CurrentAction = (decision.CanSense ? AIBehaviorAction.Pursue : AIBehaviorAction.Search); } else if (data.CurrentState == VanillaAlertness.Engaged) { data.CurrentAction = AIBehaviorAction.Attack; } decision.RecommendedAction = data.CurrentAction; data.TimeInCurrentAction += Time.deltaTime; if (decision.GroupBehavior.CallForHelp && data.CurrentState >= VanillaAlertness.Alerted) { Character component = ai.GetComponent(); if (component != null) { BehaviorSystem.CallNearbyAllies(component, component.transform.position, decision.GroupBehavior.AlertRadius, p); } } } private static void FinalizeDecision(AwarenessData data, StealthDecision decision) { decision.Confidence = data.LastConfidence; decision.Confidence = Mathf.Clamp01(decision.Confidence); decision.DecisionReason = GenerateDecisionReason(data, decision); data.LastDecisionReason = decision.DecisionReason; data.LastConfidence = decision.Confidence; decision.Debug.DecisionReason = decision.DecisionReason; decision.Debug.FullDecisionTrace = BuildDecisionTrace(data, decision); } private static Vector3 GetFlankingDirection(Vector3 aiPos, Vector3 playerPos) { Vector3 normalized = (playerPos - aiPos).normalized; Vector3 vector = new Vector3(0f - normalized.z, 0f, normalized.x); if (!(UnityEngine.Random.value > 0.5f)) { return -vector; } return vector; } private static string GenerateDecisionReason(AwarenessData data, StealthDecision decision) { if (decision.Flee.ShouldFlee) { if (!decision.Flee.IsStrategicRetreat) { return $"Fleeing (Health: {data.SelfHealthPercent:P})"; } return $"Strategic retreat (Health: {data.SelfHealthPercent:P})"; } if (decision.ShouldGiveUp) { return "Giving up - lost target"; } return data.CurrentState switch { VanillaAlertness.Unaware => "Unaware - idle", VanillaAlertness.Suspicious => decision.CanHear ? "Suspicious - heard sound" : "Suspicious - investigating", VanillaAlertness.Alerted => decision.CanSee ? "Alerted - pursuing" : "Alerted - searching", VanillaAlertness.Engaged => decision.GroupBehavior.CallForHelp ? "Engaged - calling allies" : "Engaged - attacking", _ => "Unknown state", }; } private static string BuildDecisionTrace(AwarenessData data, StealthDecision decision) { return $"[State:{data.CurrentState}|Det:{decision.DetectionLevel:F2}|" + $"Act:{data.CurrentAction}|HP:{data.SelfHealthPercent:P}|" + $"Flee:{decision.Flee.ShouldFlee}|Allies:{data.NearbyAlliesCount}]"; } private static void CleanupStaleCache(int currentFrame) { List list = new List(); foreach (KeyValuePair item in _evalCache) { if (currentFrame - item.Value.LastFullEvalFrame > 600) { list.Add(item.Key); } } foreach (StableAIKey item2 in list) { _evalCache.Remove(item2); } } public static void ClearCache(MonsterAI ai) { if (!(ai != null)) { return; } int instanceID = ai.GetInstanceID(); StableAIKey key = new StableAIKey(0uL, instanceID); if (_evalCache.ContainsKey(key)) { _evalCache.Remove(key); } try { ZNetView component = ai.GetComponent(); if (!(component != null)) { return; } ZDO zDO = component.GetZDO(); if (zDO != null) { StableAIKey key2 = new StableAIKey((ulong)zDO.m_uid.UserID, instanceID); if (_evalCache.ContainsKey(key2)) { _evalCache.Remove(key2); } } } catch { } } public static void ClearAllCaches() { _evalCache.Clear(); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.NoiseMeter ---- namespace ShadowsOfMidgard { public class NoiseMeter : MonoBehaviour, IBeginDragHandler, IEventSystemHandler, IDragHandler, IEndDragHandler { private Image _background; private Image _fill; private RectTransform _rect; private RectTransform _fillRect; private float _smoothNoise; private const float BAR_WIDTH = 60f; private const float BAR_HEIGHT = 8f; public static NoiseMeter Create() { GameObject obj = new GameObject("NoiseMeter"); obj.transform.SetParent(StealthUIRoot.Instance.canvas.transform, worldPositionStays: false); NoiseMeter noiseMeter = obj.AddComponent(); noiseMeter.Build(); return noiseMeter; } private void Build() { _background = base.gameObject.AddComponent(); _background.color = new Color(0.1f, 0.1f, 0.1f, 0.7f); _background.raycastTarget = true; _rect = _background.rectTransform; _rect.sizeDelta = new Vector2(60f, 8f); _rect.anchoredPosition = new Vector2(ShadowsOfMidgardConfig.UI_NoisePosX.Value, ShadowsOfMidgardConfig.UI_NoisePosY.Value); GameObject gameObject = new GameObject("NoiseFill"); gameObject.transform.SetParent(base.transform, worldPositionStays: false); _fill = gameObject.AddComponent(); _fill.color = Color.green; _fill.raycastTarget = false; _fillRect = _fill.rectTransform; _fillRect.anchorMin = new Vector2(0f, 0f); _fillRect.anchorMax = new Vector2(0f, 1f); _fillRect.pivot = new Vector2(0f, 0.5f); _fillRect.offsetMin = new Vector2(1f, 1f); _fillRect.offsetMax = new Vector2(1f, -1f); _fillRect.sizeDelta = new Vector2(0f, 0f); } public void UpdateNoise(float noise) { _smoothNoise = Mathf.Lerp(_smoothNoise, noise, Time.deltaTime * 8f); float x = Mathf.Clamp01(_smoothNoise) * 58f; _fillRect.sizeDelta = new Vector2(x, 0f); if (_smoothNoise < 0.33f) { _fill.color = Color.Lerp(Color.green, Color.yellow, _smoothNoise / 0.33f); } else if (_smoothNoise < 0.66f) { _fill.color = Color.Lerp(Color.yellow, new Color(1f, 0.5f, 0f), (_smoothNoise - 0.33f) / 0.33f); } else { _fill.color = Color.Lerp(new Color(1f, 0.5f, 0f), Color.red, (_smoothNoise - 0.66f) / 0.34f); } } public void SetVisible(bool visible) { base.gameObject.SetActive(visible); } public void OnBeginDrag(PointerEventData eventData) { } public void OnDrag(PointerEventData eventData) { _rect.anchoredPosition += eventData.delta; } public void OnEndDrag(PointerEventData eventData) { ShadowsOfMidgardConfig.UI_NoisePosX.Value = _rect.anchoredPosition.x; ShadowsOfMidgardConfig.UI_NoisePosY.Value = _rect.anchoredPosition.y; ShadowsOfMidgardConfig.UI_NoisePosX.ConfigFile.Save(); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.SpriteLoader ---- namespace ShadowsOfMidgard { public static class SpriteLoader { private static readonly Dictionary Cache = new Dictionary(); private static MethodInfo _loadImageMethod; public static Sprite LoadSprite(string fileName) { if (Cache.TryGetValue(fileName, out var value)) { return value; } string text = Path.Combine(Paths.PluginPath, "ShadowsOfMidgard", "Assets", fileName); if (File.Exists(text)) { try { Texture2D texture2D = DecodePNG(File.ReadAllBytes(text)); if (texture2D != null) { Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f)); Cache[fileName] = sprite; return sprite; } Debug.LogWarning("SpriteLoader: Failed to decode PNG from disk: " + fileName + " - will attempt embedded resource."); } catch (Exception arg) { Debug.LogWarning($"SpriteLoader: Error reading disk file {text}: {arg}"); } } byte[] embeddedResourceBytes = GetEmbeddedResourceBytes(fileName); if (embeddedResourceBytes != null) { Texture2D texture2D2 = DecodePNG(embeddedResourceBytes); if (texture2D2 == null) { Debug.LogError("SpriteLoader: Failed to decode embedded PNG: " + fileName); return null; } try { Directory.CreateDirectory(Path.GetDirectoryName(text)); File.WriteAllBytes(text, embeddedResourceBytes); } catch (Exception arg2) { Debug.LogWarning($"SpriteLoader: Could not write embedded asset to disk {text}: {arg2}"); } Sprite sprite2 = Sprite.Create(texture2D2, new Rect(0f, 0f, texture2D2.width, texture2D2.height), new Vector2(0.5f, 0.5f)); Cache[fileName] = sprite2; return sprite2; } Debug.LogError("SpriteLoader: File not found and embedded resource missing: " + text); return null; } public static Sprite LoadEmbeddedPNG(string resourceName) { if (Cache.TryGetValue(resourceName, out var value)) { return value; } byte[] embeddedResourceBytes = GetEmbeddedResourceBytes(resourceName); if (embeddedResourceBytes == null) { Debug.LogError("SpriteLoader: Embedded resource not found: " + resourceName); return null; } Texture2D texture2D = DecodePNG(embeddedResourceBytes); if (texture2D == null) { Debug.LogError("SpriteLoader: Failed to decode embedded PNG: " + resourceName); return null; } Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f)); Cache[resourceName] = sprite; return sprite; } private static byte[] GetEmbeddedResourceBytes(string resourceName) { Assembly executingAssembly = Assembly.GetExecutingAssembly(); try { Stream manifestResourceStream = executingAssembly.GetManifestResourceStream(resourceName); if (manifestResourceStream == null) { string[] manifestResourceNames = executingAssembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (text.EndsWith(resourceName, StringComparison.OrdinalIgnoreCase)) { manifestResourceStream = executingAssembly.GetManifestResourceStream(text); break; } } } if (manifestResourceStream == null) { return null; } using (manifestResourceStream) { using MemoryStream memoryStream = new MemoryStream(); manifestResourceStream.CopyTo(memoryStream); return memoryStream.ToArray(); } } catch (Exception arg) { Debug.LogWarning($"SpriteLoader: Error reading embedded resource {resourceName}: {arg}"); return null; } } private static Texture2D DecodePNG(byte[] data) { try { if (_loadImageMethod == null) { Type type = typeof(Texture2D).Assembly.GetType("UnityEngine.ImageConversion"); if (type != null) { _loadImageMethod = type.GetMethod("LoadImage", new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }) ?? type.GetMethod("LoadImage", new Type[2] { typeof(Texture2D), typeof(byte[]) }); } } if (_loadImageMethod == null) { Debug.LogError("SpriteLoader: ImageConversion.LoadImage method not found via reflection."); return null; } Texture2D texture2D = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false); object[] parameters = ((_loadImageMethod.GetParameters().Length != 3) ? new object[2] { texture2D, data } : new object[3] { texture2D, data, false }); _loadImageMethod.Invoke(null, parameters); return texture2D; } catch (Exception arg) { Debug.LogError($"SpriteLoader: PNG decode failed: {arg}"); return null; } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthGem ---- namespace ShadowsOfMidgard { public class StealthGem : MonoBehaviour, IBeginDragHandler, IEventSystemHandler, IDragHandler, IEndDragHandler { private Image _gem; private RectTransform _rect; private float _smoothVis = 1f; private float _pulseTimer; private static Sprite _eyeSprite; public static StealthGem Create() { GameObject obj = new GameObject("StealthGem"); obj.transform.SetParent(StealthUIRoot.Instance.canvas.transform, worldPositionStays: false); StealthGem stealthGem = obj.AddComponent(); stealthGem.Build(); return stealthGem; } private void Build() { LoadSprites(); _gem = base.gameObject.AddComponent(); _rect = _gem.rectTransform; _gem.sprite = _eyeSprite; _gem.color = Color.white; _gem.raycastTarget = true; _rect.sizeDelta = new Vector2(40f, 40f); _rect.anchoredPosition = new Vector2(ShadowsOfMidgardConfig.UI_EyePosX.Value, ShadowsOfMidgardConfig.UI_EyePosY.Value); } private void LoadSprites() { if (_eyeSprite == null) { _eyeSprite = SpriteLoader.LoadEmbeddedPNG("eye.png"); if (_eyeSprite == null) { _eyeSprite = SpriteLoader.LoadSprite("eye.png"); } if (_eyeSprite == null) { Debug.LogError("StealthGem: eye.png failed to load (embedded and disk lookup failed)!"); } } } public void UpdateGem(float visibility, VanillaAlertness state) { if (state != VanillaAlertness.Engaged) { _smoothVis = Mathf.Lerp(_smoothVis, visibility, Time.deltaTime * 8f); float num = Mathf.Lerp(0.5f, 1.4f, _smoothVis / 2f); _rect.localScale = new Vector3(num, num, 1f); } switch (state) { case VanillaAlertness.Unaware: _gem.color = Color.blue; break; case VanillaAlertness.Suspicious: _gem.color = Color.yellow; break; case VanillaAlertness.Alerted: _gem.color = Color.red; break; case VanillaAlertness.Engaged: _gem.color = Color.red; Pulse(); break; } } private void Pulse() { _pulseTimer += Time.deltaTime * 6f; float t = (Mathf.Sin(_pulseTimer) + 1f) * 0.5f; float num = Mathf.Lerp(1f, 1.3f, t); _rect.localScale = new Vector3(num, num, 1f); } public void SetVisible(bool visible) { base.gameObject.SetActive(visible); } public void OnBeginDrag(PointerEventData eventData) { } public void OnDrag(PointerEventData eventData) { _rect.anchoredPosition += eventData.delta; } public void OnEndDrag(PointerEventData eventData) { ShadowsOfMidgardConfig.UI_EyePosX.Value = _rect.anchoredPosition.x; ShadowsOfMidgardConfig.UI_EyePosY.Value = _rect.anchoredPosition.y; ShadowsOfMidgardConfig.UI_EyePosX.ConfigFile.Save(); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthUIController ---- namespace ShadowsOfMidgard { public class StealthUIController : MonoBehaviour { private StealthGem _gem; private NoiseMeter _noiseMeter; private bool _initialized; public static StealthUIController Instance { get; private set; } public static void Init() { if (!(Instance != null)) { GameObject obj = new GameObject("StealthUIController"); Object.DontDestroyOnLoad(obj); Instance = obj.AddComponent(); } } private void Update() { Player localPlayer = Player.m_localPlayer; if (!localPlayer) { return; } if (!ShadowsOfMidgardConfig.DebugVision.Value) { if (_initialized) { if (_gem != null) { _gem.SetVisible(visible: false); } if (_noiseMeter != null) { _noiseMeter.SetVisible(visible: false); } } return; } if (!_initialized) { _gem = StealthGem.Create(); _noiseMeter = NoiseMeter.Create(); _initialized = true; } _gem.SetVisible(visible: true); _noiseMeter.SetVisible(visible: true); float visibility = VisibilitySystem.GetVisibility(localPlayer); float noise = NoiseSystem.GetNoise(localPlayer); VanillaAlertness highestAlertness = GetHighestAlertness(); _gem.UpdateGem(visibility, highestAlertness); _noiseMeter.UpdateNoise(noise); } private VanillaAlertness GetHighestAlertness() { VanillaAlertness vanillaAlertness = VanillaAlertness.Unaware; foreach (KeyValuePair allDatum in AwarenessSystem.GetAllData()) { AwarenessData value = allDatum.Value; if (value != null && value.CurrentState > vanillaAlertness) { vanillaAlertness = value.CurrentState; } } return vanillaAlertness; } public void OnAwarenessUpdated(Character enemy, AwarenessData data, VanillaAlertness state) { } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthUIRoot ---- namespace ShadowsOfMidgard { public class StealthUIRoot : MonoBehaviour { public static StealthUIRoot Instance; public Canvas canvas; private void Awake() { if (Instance != null) { Object.Destroy(base.gameObject); return; } Instance = this; Object.DontDestroyOnLoad(base.gameObject); canvas = base.gameObject.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 9999; CanvasScaler canvasScaler = base.gameObject.AddComponent(); canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; canvasScaler.referenceResolution = new Vector2(1920f, 1080f); if (!base.gameObject.TryGetComponent(out var _)) { base.gameObject.AddComponent(); } if (Object.FindAnyObjectByType() == null) { GameObject obj = new GameObject("EventSystem"); obj.AddComponent(); obj.AddComponent(); Object.DontDestroyOnLoad(obj); } StealthUIController.Init(); } public static void Init() { if (!(Instance != null)) { new GameObject("StealthUIRoot").AddComponent(); } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.VanillaAlertness ---- namespace ShadowsOfMidgard { public enum VanillaAlertness { Unaware, Suspicious, Alerted, Engaged } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.AIBehaviorAction ---- namespace ShadowsOfMidgard { public enum AIBehaviorAction { Idle, Patrol, Search, Pursue, Flee, Attack, CallForHelp, Investigate, StrategicRetreat } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.MovementStrategy ---- namespace ShadowsOfMidgard { public enum MovementStrategy { None, TowardPlayer, TowardLastSeen, AwayFromPlayer, Flanking, Evasive, StrategicDist } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.TargetPriority ---- namespace ShadowsOfMidgard { public enum TargetPriority { None, Secondary, Confirmed, Immediate } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.FleeReason ---- namespace ShadowsOfMidgard { public enum FleeReason { None, HealthCritical, StrategicRetreat, Overwhelming, Grouping } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.AwarenessData ---- namespace ShadowsOfMidgard { public class AwarenessData { public float DetectionLevel; public Vector3 LastKnownPosition = Vector3.zero; public float TimeSinceSeen; public VanillaAlertness CurrentState; public AIBehaviorAction CurrentAction; public Vector3 DesiredDirection = Vector3.zero; public float DesiredSpeed; public bool ShouldRun; public Vector3 FleeDirection = Vector3.zero; public int NearbyAlliesCount; public float TimeUntilNextAllyCall; public bool IsPartOfGroup; public TargetPriority TargetPriority; public float TargetDistance = float.MaxValue; public float PlayerHealthPercent = 1f; public float SelfHealthPercent = 1f; public float AggressionLevel = 0.5f; public FleeReason CurrentFleeReason; public float FleeStartHealth = 1f; public float TimeSpentFleeing; public float TimeInCurrentState; public float TimeInCurrentAction; public string LastDecisionReason = ""; public float LastConfidence; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.MovementDirective ---- namespace ShadowsOfMidgard { public struct MovementDirective { public Vector3 Direction; public float Speed; public bool ShouldRun; public MovementStrategy Strategy; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.CombatDirective ---- namespace ShadowsOfMidgard { public struct CombatDirective { public bool ShouldAttack; public bool ShouldKeepDistance; public float OptimalAttackRange; public float AggressionLevel; public bool FavorDefense; public float DamageThreshold; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.GroupBehavior ---- namespace ShadowsOfMidgard { public struct GroupBehavior { public bool CallForHelp; public bool CoordinatedAttack; public float AlertRadius; public int MinAlliesForGroupAction; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.FleeDirective ---- namespace ShadowsOfMidgard { public struct FleeDirective { public bool ShouldFlee; public FleeReason Reason; public Vector3 FleeTarget; public float ReturnWaitTime; public float HealthThreshold; public bool IsStrategicRetreat; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthDecision ---- namespace ShadowsOfMidgard { public class StealthDecision { public bool CanSense; public bool CanSee; public bool CanHear; public float SensingConfidence; public VanillaAlertness State; public float DetectionLevel; public TargetPriority TargetPriority; public AIBehaviorAction RecommendedAction; public MovementDirective Movement; public Vector3? SearchTarget; public float SearchRadius; public bool ShouldSearch; public bool ShouldGiveUp; public CombatDirective Combat; public bool ShouldPursue; public FleeDirective Flee; public float FleeConfidence; public GroupBehavior GroupBehavior; public float Confidence; public string DecisionReason; public int FrameCounter; public StealthDebugData Debug; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.StealthDebugData ---- namespace ShadowsOfMidgard { public struct StealthDebugData { public float Visibility; public float Noise; public float Hiding; public float Camo; public bool CanSee; public bool CanHear; public bool CanSense; public VanillaAlertness State; public float DetectionLevel; public Vector3? LastKnownPosition; public Vector3? SearchTarget; public Vector3? SuggestedDirection; public float? SuggestedSpeed; public MovementStrategy? Strategy; public float? AggressionLevel; public float? SelfHealthPercent; public float? PlayerHealthPercent; public FleeReason? FleeReason; public float? FleeThreshold; public bool? IsStrategicRetreat; public int? NearbyAlliesCount; public bool? ShouldCallForHelp; public float? AlertRadius; public float OverallConfidence; public string DecisionReason; public string FullDecisionTrace; } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.VisibilitySystem ---- namespace ShadowsOfMidgard { public static class VisibilitySystem { private static int _vegetationMask = -1; private static int VegetationMask { get { if (_vegetationMask == -1) { _vegetationMask = LayerMask.GetMask("Default", "piece", "piece_nonsolid", "static_solid", "terrain"); } return _vegetationMask; } } public static float GetVisibility(Player p) { StealthConfigModel active = ShadowsOfMidgardConfig.Active; if (p == null || active == null || !active.EnableVisibilitySystem) { return 1f; } float num = LightExposure(p, active); num -= ShadowBonus(p, active); num -= GrassBonus(p, active); num -= WeatherReduction(active); num += MovementPenalty(p, active); num += ArmorVisibility(p, active); num = Mathf.Clamp01(num); if (ShadowsOfMidgardConfig.DebugVision.Value) { Debug.Log($"[Visibility] {p.name}: vis={num:F2} (light={LightExposure(p, active):F2} shadow={ShadowBonus(p, active):F2} weather={WeatherReduction(active):F2} armor={ArmorVisibility(p, active):F2})"); } return num; } private static float LightExposure(Player p, StealthConfigModel cfg) { float value = ((RenderSettings.sun != null) ? RenderSettings.sun.intensity : 0.5f); float lightPenalty = cfg.LightPenalty; return Mathf.Clamp01(0.15f + lightPenalty * Mathf.Clamp01(value)); } private static float ShadowBonus(Player p, StealthConfigModel cfg) { if (RenderSettings.sun == null) { return 0f; } Vector3 origin = p.transform.position + Vector3.up * 1.5f; Vector3 direction = -RenderSettings.sun.transform.forward; if (!Physics.Raycast(origin, direction, 20f, VegetationMask)) { return 0f; } return cfg.ShadowBonus; } private static float GrassBonus(Player p, StealthConfigModel cfg) { Collider[] array = Physics.OverlapSphere(p.transform.position, 1f, VegetationMask); for (int i = 0; i < array.Length; i++) { if (array[i].name.ToLowerInvariant().Contains("grass")) { return cfg.GrassBonus; } } return 0f; } private static float MovementPenalty(Player p, StealthConfigModel cfg) { return Mathf.Clamp01(p.GetVelocity().magnitude / 7f) * cfg.MovementPenalty; } private static float ArmorVisibility(Player p, StealthConfigModel cfg) { return ArmorUtils.GetTotalStealthPenalty(p) * cfg.ArmorVisibility; } private static float WeatherReduction(StealthConfigModel cfg) { float num = 0f; if (EnvironmentUtils.IsRaining() || EnvironmentUtils.IsSnowing()) { num += cfg.WeatherVisReduction; } if (EnvironmentUtils.IsMisty()) { num += cfg.FogVisReduction; } float fogDensity = EnvironmentUtils.GetFogDensity(); if (fogDensity > 0.01f) { num += fogDensity * cfg.FogVisReduction; } return Mathf.Clamp01(num); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.EnvironmentUtils ---- namespace ShadowsOfMidgard { public static class EnvironmentUtils { public static float GetFogDensity() { return Mathf.Clamp01(RenderSettings.fogDensity); } public static bool IsRaining() { return (EnvMan.instance?.GetCurrentEnvironment()?.m_name.ToLowerInvariant() ?? "").Contains("rain"); } public static bool IsSnowing() { return (EnvMan.instance?.GetCurrentEnvironment()?.m_name.ToLowerInvariant() ?? "").Contains("snow"); } public static bool IsMisty() { return (EnvMan.instance?.GetCurrentEnvironment()?.m_name.ToLowerInvariant() ?? "").Contains("mist"); } public static float GetAmbientLight() { return Mathf.Clamp(RenderSettings.ambientIntensity, 0f, 2f); } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.RaycastUtils ---- namespace ShadowsOfMidgard { public static class RaycastUtils { private static int _terrainMask = -1; private static int TerrainMask { get { if (_terrainMask == -1) { _terrainMask = LayerMask.GetMask("Default", "terrain", "piece", "piece_nonsolid", "vegetation"); } return _terrainMask; } } public static bool HasLineOfSight(Vector3 from, Vector3 to) { if (Physics.Linecast(from, to, out var hitInfo, TerrainMask)) { if (!(hitInfo.collider.GetComponent() != null)) { return hitInfo.collider.GetComponent() != null; } return true; } return true; } public static bool SkyVisible(Vector3 pos) { return !Physics.Raycast(pos, Vector3.up, 50f, TerrainMask); } public static float SampleOcclusion(Vector3 pos, float radius = 1.5f) { int num = 0; int num2 = 6; Vector3[] array = new Vector3[6] { Vector3.forward, Vector3.back, Vector3.left, Vector3.right, (Vector3.forward + Vector3.right).normalized, (Vector3.forward - Vector3.right).normalized }; foreach (Vector3 direction in array) { if (Physics.Raycast(pos, direction, radius, TerrainMask)) { num++; } } return (float)num / (float)num2; } public static bool InTallGrass(Vector3 pos) { if (Physics.SphereCast(pos + Vector3.up * 0.5f, 0.5f, Vector3.down, out var hitInfo, 1f, TerrainMask)) { string text = hitInfo.collider.name.ToLowerInvariant(); if (!text.Contains("grass") && !text.Contains("bush")) { return text.Contains("plant"); } return true; } return false; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.BaseAI_SetMoveDir_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(Character), "SetMoveDir")] public static class BaseAI_SetMoveDir_Patch { public static bool Prefix(Character __instance, ref Vector3 dir) { if (__instance == null || __instance is Player) { return true; } BaseAI component = __instance.GetComponent(); if (component == null) { return true; } if (!AIAuthority.IsAuthoritative(component)) { return true; } if (__instance.IsDead()) { return true; } AwarenessData data = AwarenessSystem.GetData(__instance); if (data == null) { return true; } switch (data.CurrentState) { case VanillaAlertness.Unaware: return true; case VanillaAlertness.Suspicious: if (dir.magnitude > 0.5f) { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value) { ShadowsOfMidgard.Log.LogInfo($"[Movement Limited] {__instance.name}: SUSPICIOUS → slow walk (was={dir.magnitude:F2})"); } dir = dir.normalized * 0.3f; } return true; case VanillaAlertness.Alerted: if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && dir.magnitude > 0.7f) { ShadowsOfMidgard.Log.LogInfo("[Movement] " + __instance.name + ": ALERTED → full pursuit"); } return true; case VanillaAlertness.Engaged: return true; default: return true; } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.BaseAI_StealthBrain_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(BaseAI), "CanSenseTarget", new Type[] { typeof(Character) })] public static class BaseAI_StealthBrain_Patch { public static bool Prefix(BaseAI __instance, Character target, ref bool __result) { if (!AIAuthority.IsAuthoritative(__instance)) { return true; } if (target == null || !target.IsPlayer()) { return true; } MonsterAI monsterAI = __instance as MonsterAI; if (monsterAI == null) { return true; } Player player = target as Player; if (player == null) { return true; } Character component = __instance.GetComponent(); if (component != null) { AwarenessData data = AwarenessSystem.GetData(component); if (data != null && data.CurrentState >= VanillaAlertness.Alerted) { __result = true; return false; } } StealthDecision stealthDecision = StealthBrain.Evaluate(monsterAI, player, Time.deltaTime); __result = stealthDecision.CanSense; try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { Character component2 = __instance.GetComponent(); string text = ((component2 != null) ? component2.name : __instance.name); int frameCount = Time.frameCount; int num = ((component2 != null) ? component2.GetInstanceID() : __instance.GetInstanceID()); ShadowsOfMidgard.Log?.LogInfo($"[CanSenseOverride] frame={frameCount} ai={num} {text}: CanSense={__result} Det={stealthDecision.DetectionLevel:F2} State={stealthDecision.State}"); } } catch { } return false; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.BaseAI_StealthBrain_IsAlerted_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(BaseAI), "IsAlerted")] public static class BaseAI_StealthBrain_IsAlerted_Patch { public static bool Prefix(BaseAI __instance, ref bool __result) { if (!AIAuthority.IsAuthoritative(__instance)) { return true; } if (!(__instance is MonsterAI)) { return true; } Character component = __instance.GetComponent(); if (component == null || component.IsDead()) { return true; } AwarenessData data = AwarenessSystem.GetData(component); if (data == null) { return true; } __result = data.CurrentState == VanillaAlertness.Alerted || data.CurrentState == VanillaAlertness.Engaged; return false; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.Character_Damage_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(Character), "Damage")] public static class Character_Damage_Patch { public static void Postfix(Character __instance, HitData hit) { if (__instance == null || __instance.IsDead() || __instance is Player) { return; } BaseAI component = __instance.GetComponent(); if (component == null || !AIAuthority.IsAuthoritative(component)) { return; } AwarenessData data = AwarenessSystem.GetData(__instance); if (data != null) { data.CurrentState = VanillaAlertness.Alerted; data.DetectionLevel = Mathf.Max(data.DetectionLevel, 0.5f); if (hit != null && hit.GetAttacker() != null) { data.LastKnownPosition = hit.GetAttacker().transform.position; } data.TimeSinceSeen = 0f; } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.CharacterOnDamagedPatch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(Character), "OnDamaged")] public static class CharacterOnDamagedPatch { public static void Postfix(Character __instance, HitData hit) { if (__instance == null || __instance.IsDead() || __instance is Player) { return; } BaseAI component = __instance.GetComponent(); if (component == null || !AIAuthority.IsAuthoritative(component)) { return; } AwarenessData data = AwarenessSystem.GetData(__instance); if (data != null) { data.DetectionLevel = Mathf.Max(data.DetectionLevel, 0.8f); if (data.DetectionLevel >= 0.75f) { data.CurrentState = VanillaAlertness.Engaged; } else if (data.CurrentState < VanillaAlertness.Alerted) { data.CurrentState = VanillaAlertness.Alerted; } data.TimeSinceSeen = 0f; } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.Humanoid_Attack_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(Humanoid), "StartAttack")] public static class Humanoid_Attack_Patch { private static readonly float ATTACK_COOLDOWN = 1.5f; private static Dictionary _attackTimers = new Dictionary(); public static bool Prefix(Humanoid __instance) { if (__instance is Player) { return true; } BaseAI component = __instance.GetComponent(); if (component == null) { return true; } if (!AIAuthority.IsAuthoritative(component)) { return true; } if (__instance == null || __instance.IsDead()) { return true; } AwarenessData data = AwarenessSystem.GetData(__instance); if (data == null) { return false; } if (data.CurrentState < VanillaAlertness.Alerted) { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount = Time.frameCount; int num = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log.LogInfo($"[Combat Block] frame={frameCount} ai={num} {__instance.name}: State={data.CurrentState} (below Alerted)"); } return false; } if (data.CurrentFleeReason != FleeReason.None) { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount2 = Time.frameCount; int num2 = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log.LogInfo($"[Combat Block] frame={frameCount2} ai={num2} {__instance.name}: Fleeing (reason={data.CurrentFleeReason})"); } return false; } float num3 = Time.time - GetLastAttackTime(__instance); float num4 = ATTACK_COOLDOWN / Mathf.Max(0.5f, data.AggressionLevel); if (num3 < num4) { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount3 = Time.frameCount; int num5 = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log.LogInfo($"[Combat Cooldown] frame={frameCount3} ai={num5} {__instance.name}: {num3:F2}s / {num4:F2}s"); } return false; } MonsterAI monsterAI = component as MonsterAI; if (monsterAI == null) { monsterAI = __instance.GetComponent(); } if (monsterAI == null) { return false; } Player player = monsterAI.GetTargetCreature() as Player; if (player == null || player.IsDead()) { return true; } StealthDecision stealthDecision = StealthBrain.Evaluate(monsterAI, player, Time.deltaTime); try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount4 = Time.frameCount; int num6 = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log.LogInfo($"[CombatCheck] frame={frameCount4} ai={num6} {__instance.name}: State={stealthDecision.State} Det={stealthDecision.DetectionLevel:F2} ShouldAttack={stealthDecision.Combat.ShouldAttack} Target={player?.name}"); } } catch { } if (!stealthDecision.Combat.ShouldAttack) { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value) { ShadowsOfMidgard.Log.LogInfo("[Combat Block] " + __instance.name + ": CombatDirective.ShouldAttack=false"); } return false; } SetLastAttackTime(__instance, Time.time); if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { float aggressionLevel = stealthDecision.Combat.AggressionLevel; string text = (stealthDecision.Combat.FavorDefense ? "DEFENSIVE" : "OFFENSIVE"); int frameCount5 = Time.frameCount; int num7 = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log.LogInfo($"[Combat ALLOWED] frame={frameCount5} ai={num7} {__instance.name}: Aggression={aggressionLevel:F2} Stance={text}"); } return true; } private static float GetLastAttackTime(Character c) { if (c == null) { return -999f; } object obj = c.GetComponent(); if (obj == null) { obj = c; } if (!_attackTimers.ContainsKey(obj)) { _attackTimers[obj] = -999f; } return _attackTimers[obj]; } private static void SetLastAttackTime(Character c, float time) { if (!(c == null)) { object obj = c.GetComponent(); if (obj == null) { obj = c; } _attackTimers[obj] = time; } } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.MonsterAI_StealthBrain_UpdateAI_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] public static class MonsterAI_StealthBrain_UpdateAI_Patch { public static bool Prefix(MonsterAI __instance, float dt) { if (!AIAuthority.IsAuthoritative(__instance)) { return true; } Character targetCreature = __instance.GetTargetCreature(); if (targetCreature == null) { return true; } Player player = targetCreature as Player; if (player == null || player.IsDead()) { return true; } StealthDecision stealthDecision = StealthBrain.Evaluate(__instance, player, dt); AwarenessData data = AwarenessSystem.GetData(__instance.GetComponent()); if (data != null) { data.CurrentState = stealthDecision.State; data.DetectionLevel = stealthDecision.DetectionLevel; if (stealthDecision.ShouldSearch) { data.LastKnownPosition = stealthDecision.SearchTarget ?? player.transform.position; } if (stealthDecision.ShouldGiveUp) { data.LastKnownPosition = Vector3.zero; data.TimeSinceSeen = 0f; } try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount = Time.frameCount; int num = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log?.LogInfo($"[WRITE-AWARENESS] frame={frameCount} ai={num} {__instance.name}: WriteState={data.CurrentState} WriteDet={data.DetectionLevel:F2} LastKnown={data.LastKnownPosition}"); } } catch { } try { if (ShadowsOfMidgardConfig.DebugVision != null && ShadowsOfMidgardConfig.DebugVision.Value && ShadowsOfMidgardConfig.DebugAI != null && ShadowsOfMidgardConfig.DebugAI.Value) { int frameCount2 = Time.frameCount; int num2 = ((__instance != null) ? __instance.GetInstanceID() : 0); ShadowsOfMidgard.Log?.LogInfo($"[UpdateAI] frame={frameCount2} ai={num2} {__instance.name}: State={stealthDecision.State} Det={stealthDecision.DetectionLevel:F2} Search={stealthDecision.ShouldSearch} GiveUp={stealthDecision.ShouldGiveUp}"); } } catch { } } return true; } } } // ---- ShadowsOfMidgard/plugins/ShadowsOfMidgard.dll :: ShadowsOfMidgard.Patches.MonsterAI_StealthBrain_UpdateTarget_Patch ---- namespace ShadowsOfMidgard.Patches { [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] public static class MonsterAI_StealthBrain_UpdateTarget_Patch { public static bool Prefix(MonsterAI __instance, Humanoid humanoid, float dt, ref bool canHearTarget, ref bool canSeeTarget) { if (!AIAuthority.IsAuthoritative(__instance)) { return true; } Player player = humanoid as Player; if (player == null) { return true; } StealthDecision stealthDecision = StealthBrain.Evaluate(__instance, player, dt); canSeeTarget = stealthDecision.CanSee; canHearTarget = stealthDecision.CanHear; if (stealthDecision.CanSense) { ZNetView component = player.GetComponent(); ZDO zDO = ((component != null) ? component.GetZDO() : null); if (zDO != null) { ((BaseAI)__instance).SetTargetInfo(zDO.m_uid); } } if (stealthDecision.ShouldGiveUp) { ((BaseAI)__instance).SetTargetInfo(ZDOID.None); } return true; } } }