Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Valheim Foresight v2.0.1
BepInEx/plugins/Valheim.Foresight.dll
Decompiled 3 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; using Valheim.Foresight.Components; using Valheim.Foresight.Configuration; using Valheim.Foresight.Core; using Valheim.Foresight.HarmonyRefs; using Valheim.Foresight.Models; using Valheim.Foresight.Patches; using Valheim.Foresight.Services.Castbar; using Valheim.Foresight.Services.Castbar.Interfaces; using Valheim.Foresight.Services.Combat; using Valheim.Foresight.Services.Combat.Interfaces; using Valheim.Foresight.Services.Combat.Wrappers; using Valheim.Foresight.Services.Damage; using Valheim.Foresight.Services.Hud; using Valheim.Foresight.Services.Hud.Interfaces; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Valheim Foresight")] [assembly: AssemblyDescription("Combat threat assessment mod for Valheim")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("CoffeeNova")] [assembly: AssemblyProduct("Valheim Foresight")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("AA62F894-B9B4-4C38-95B2-F74902F5E0A9")] [assembly: AssemblyFileVersion("2.0.1")] [assembly: AssemblyInformationalVersion("2.0.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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; } } } public sealed class AttackKeyComparer : IEqualityComparer<AttackKey> { public static readonly AttackKeyComparer Instance = new AttackKeyComparer(); public bool Equals(AttackKey x, AttackKey y) { if (string.Equals(x.CreaturePrefab, y.CreaturePrefab, StringComparison.OrdinalIgnoreCase)) { return string.Equals(x.AttackAnimation, y.AttackAnimation, StringComparison.OrdinalIgnoreCase); } return false; } public int GetHashCode(AttackKey obj) { return HashCode.Combine<int, int>(StringComparer.OrdinalIgnoreCase.GetHashCode(obj.CreaturePrefab), StringComparer.OrdinalIgnoreCase.GetHashCode(obj.AttackAnimation)); } } namespace Valheim.Foresight { [BepInPlugin("coffeenova.valheim.foresight", "Valheim Foresight", "2.0.1")] public sealed class ValheimForesightPlugin : BaseUnityPlugin { internal static ILogger Log; private static IThreatIconSpriteProvider? _spriteProvider; private static AttackTimingEditorManager? _editorManager; private static ValheimForesightPlugin? _instance; private static Harmony? _harmony; private readonly Dictionary<Character?, ThreatAssessment?> _threatCache = new Dictionary<Character, ThreatAssessment>(); private ForesightConfiguration _config; private AttackOverridesConfig _attackConfig; private IThreatCalculationService _threatService; private IDifficultyMultiplierCalculator _difficultyCalculator; private IThreatResponseHintService _threatResponseHintService; private IThreatHudIconRenderer? _hudIconRenderer; private float _playerLogTimer; private float _enemyUpdateTimer; private bool _isPluginActive; public static bool InstanceDebugHudEnabled => _instance?._config.DebugEnabled.Value ?? false; public static IForesightConfiguration? ForesightConfig => _instance?._config; public static IAttackOverridesConfig? AttackOverridesConfig => _instance?._attackConfig; public static IActiveAttackTracker? ActiveAttackTracker { get; private set; } public static IUnityCastbarRenderer? CastbarRenderer { get; private set; } public static IAttackTimingService? AttackTimingService { get; private set; } internal static IThreatResponseHintService ThreatResponseHintService => _instance?._threatResponseHintService ?? throw new InvalidOperationException("ThreatResponseHintService not initialized."); internal static IThreatHudIconRenderer? HudIconRenderer => _instance?._hudIconRenderer; public static bool TryGetThreatAssessment(Character? character, out ThreatAssessment? assessment) { assessment = null; if (_instance == null || character == null) { return false; } return _instance._threatCache.TryGetValue(character, out assessment); } private void Awake() { _instance = this; _isPluginActive = true; InitializeServices(); ApplyHarmonyPatches(); LogDifficultySettings(); string[] manifestResourceNames = typeof(ValheimForesightPlugin).Assembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { Log.LogDebug("Embedded: " + text); } Log.LogInfo("Valheim Foresight 2.0.1 loaded"); } private void OnDestroy() { Log.LogInfo("Valheim Foresight 2.0.1 unloading..."); _config.SettingsChanged -= OnConfigurationChanged; DisablePlugin(); if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } Log.LogInfo("Valheim Foresight 2.0.1 unloaded"); Log = null; } private void InitializeServices() { _config = new ForesightConfiguration(((BaseUnityPlugin)this).Config); _config.SettingsChanged += OnConfigurationChanged; _attackConfig = new AttackOverridesConfig(((BaseUnityPlugin)this).Config); Log = new ForesightLogger(((BaseUnityPlugin)this).Logger) { IsLogsEnabled = _config.IsLogsEnabled.Value, IsDebugLogsEnabled = _config.DebugEnabled.Value }; BlockDamageEstimator blockEstimator = new BlockDamageEstimator(Log); ParryDamageEstimator parryEstimator = new ParryDamageEstimator(Log); Lazy<ICreatureAttackInspector> attackInspector = new Lazy<ICreatureAttackInspector>(delegate { if ((Object)(object)ZNetScene.instance == (Object)null) { Log.LogError("ZNetScene not found"); return null; } return new CreatureAttackInspector(new ZNetSceneWrapper(ZNetScene.instance), Log); }); PlayerWrapper playerWrapper = new PlayerWrapper(); ZoneSystemWrapper zoneSystemWrapper = new ZoneSystemWrapper(); MathfWrapper mathfWrapper = new MathfWrapper(); Vector3Wrapper vector3Wrapper = new Vector3Wrapper(); _difficultyCalculator = new DifficultyMultiplierCalculator(Log, playerWrapper, zoneSystemWrapper, mathfWrapper); _threatService = new ThreatCalculationService(Log, blockEstimator, parryEstimator, attackInspector, _difficultyCalculator, vector3Wrapper, mathfWrapper); _threatResponseHintService = new ThreatResponseHintService(); _spriteProvider = new EmbeddedPngSpriteProvider(new AssemblyEmbeddedResourceStreamProvider(typeof(ValheimForesightPlugin).Assembly), Log); _hudIconRenderer = new UnityThreatHudIconRenderer(_spriteProvider); ActiveAttackTracker = new ActiveAttackTracker(); ParryWindowService parryWindowService = new ParryWindowService(_config); CastbarRenderer = new UnityCastbarRenderer(Log, _config, parryWindowService); AttackTimingService = new AttackTimingService(Log, _config, _attackConfig); InitializeEditorUI(); } private void InitializeEditorUI() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (AttackTimingService == null) { Log.LogError("AttackTimingService not initialized"); return; } string value = _config.TimingEditorToggleKey.Value; if (!Enum.TryParse<KeyCode>(value, ignoreCase: true, out KeyCode result)) { Log.LogWarning("Invalid key '" + value + "', using default F7"); result = (KeyCode)288; } GameObject val = new GameObject("AttackTimingEditorManager"); Object.DontDestroyOnLoad((Object)val); _editorManager = val.AddComponent<AttackTimingEditorManager>(); _editorManager.Initialize(Log, AttackTimingService, (IAttackTimingDataProvider)AttackTimingService, _config, result); Log.LogInfo($"Attack Timing Editor UI initialized ({result} to toggle, Global Learning: {_config.AttackTimingLearningEnabled.Value})"); } private void ApplyHarmonyPatches() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown Harmony val = new Harmony("coffeenova.valheim.foresight"); val.PatchAll(); _harmony = val; MethodInfo methodInfo = AccessTools.Method(typeof(EnemyHud), "LateUpdate", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(EnemyHudPatch), "LateUpdatePostfix", (Type[])null, (Type[])null); if (methodInfo == null) { Log.LogError("Failed to find EnemyHud.LateUpdate"); } else if (methodInfo2 == null) { Log.LogError("Failed to find EnemyHudPatch.LateUpdatePostfix"); } else { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo("Patched EnemyHud.LateUpdate"); } MethodInfo methodInfo3 = AccessTools.Method(typeof(Character), "CustomFixedUpdate", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(CharacterFixedUpdatePatch), "Postfix", (Type[])null, (Type[])null); if (methodInfo3 == null) { Log.LogError("Failed to find Character.CustomFixedUpdate"); return; } if (methodInfo4 == null) { Log.LogError("Failed to find CharacterFixedUpdatePatch.Postfix"); return; } val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo("Patched Character.CustomFixedUpdate"); } private void Update() { if (_config.PluginEnabled.Value != _isPluginActive) { _isPluginActive = _config.PluginEnabled.Value; if (_isPluginActive) { EnablePlugin(); } else { DisablePlugin(); } } if (_isPluginActive) { _playerLogTimer += Time.deltaTime; _enemyUpdateTimer += Time.deltaTime; _enemyUpdateTimer += Time.deltaTime; if (_playerLogTimer >= 1f) { _playerLogTimer = 0f; LogPlayerHealth(); } if (_enemyUpdateTimer >= 1f) { _enemyUpdateTimer = 0f; UpdateNearbyEnemiesThreat(); } CleanupThreatCache(); AttackTimingService?.Update(); } } private void LogPlayerHealth() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Log.LogDebug("Player not spawned yet"); } else { Log.LogDebug($"Player HP: {((Character)localPlayer).GetHealth()}"); } } private void UpdateNearbyEnemiesThreat() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } List<Character> allCharacters = Character.GetAllCharacters(); Vector3 position = ((Component)localPlayer).transform.position; foreach (Character item in allCharacters) { if (!IsValidEnemy(item)) { continue; } Vector3 val = ((Component)item).transform.position - position; if (!(((Vector3)(ref val)).sqrMagnitude > 10000f)) { ThreatAssessment threatAssessment = _threatService.CalculateThreat(item, localPlayer, detailedMode: false); if (threatAssessment != null) { _threatCache[item] = threatAssessment; } } } } private bool IsValidEnemy(Character character) { if ((Object)(object)character != (Object)null && !character.IsPlayer()) { return !string.IsNullOrEmpty(character.m_name); } return false; } private void CleanupThreatCache() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } List<Character> list = new List<Character>(); Vector3 position = ((Component)localPlayer).transform.position; foreach (KeyValuePair<Character, ThreatAssessment> item in _threatCache) { Character key = item.Key; if (ShouldRemoveFromCache(key, position, 10000f)) { list.Add(key); } } foreach (Character item2 in list) { if ((Object)(object)item2 != (Object)null) { _threatCache.Remove(item2); } } } private bool ShouldRemoveFromCache(Character? character, Vector3 playerPos, float maxDistanceSq) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || character.IsDead()) { return true; } Vector3 val = ((Component)character).transform.position - playerPos; return ((Vector3)(ref val)).sqrMagnitude > maxDistanceSq; } private void LogDifficultySettings() { if ((Object)(object)ZoneSystem.instance == (Object)null) { Log.LogDebug("[LogDifficultySettings]ZoneSystem not ready yet"); return; } float incomingDamageFactor = _difficultyCalculator.GetIncomingDamageFactor(); float enemyHealthFactor = _difficultyCalculator.GetEnemyHealthFactor(); Log.LogInfo($"Current difficulty: EnemyDamage={incomingDamageFactor:F2}x, EnemyHP={enemyHealthFactor:F2}x"); } private void OnConfigurationChanged(object? sender, EventArgs e) { Log.IsLogsEnabled = _config.IsLogsEnabled.Value; Log.IsDebugLogsEnabled = _config.DebugEnabled.Value; } private void DisablePlugin() { Log.LogInfo("Valheim Foresight disabling..."); _threatCache.Clear(); CastbarRenderer?.Dispose(); CastbarRenderer = null; _hudIconRenderer?.Dispose(); _hudIconRenderer = null; _spriteProvider?.Dispose(); _spriteProvider = null; AttackTimingService?.Dispose(); AttackTimingService = null; if ((Object)(object)_editorManager != (Object)null) { Object.Destroy((Object)(object)((Component)_editorManager).gameObject); _editorManager = null; } Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; Log.LogInfo("Valheim Foresight disabled"); } private void EnablePlugin() { Log.LogInfo("Valheim Foresight enabling..."); InitializeServices(); ApplyHarmonyPatches(); Log.LogInfo("Valheim Foresight enabled"); } } } namespace Valheim.Foresight.Autogen { internal static class PluginInfoGenerated { internal const string PluginVersion = "2.0.1"; internal const string PluginName = "Valheim Foresight"; internal const string PluginGuid = "coffeenova.valheim.foresight"; } } namespace Valheim.Foresight.Services.Hud { public sealed class AssemblyEmbeddedResourceStreamProvider : IEmbeddedResourceStreamProvider { private readonly Assembly _asm; public AssemblyEmbeddedResourceStreamProvider(Assembly asm) { _asm = asm; } public Stream? Open(string resourceName) { return _asm.GetManifestResourceStream(resourceName); } public string[] GetNames() { return _asm.GetManifestResourceNames(); } } public sealed class EmbeddedPngSpriteProvider : IThreatIconSpriteProvider, IDisposable { private readonly IEmbeddedResourceStreamProvider _resources; private readonly ILogger _log; private readonly Dictionary<ThreatResponseHint, Sprite?> _cache = new Dictionary<ThreatResponseHint, Sprite>(); private const string BlockRes = "Valheim.Foresight.Assets.Icons.block_icon.png"; private const string ParryRes = "Valheim.Foresight.Assets.Icons.parry_icon.png"; private const string DodgeRes = "Valheim.Foresight.Assets.Icons.roll_icon.png"; private readonly List<Sprite> _createdSprites = new List<Sprite>(); private readonly List<Texture2D> _createdTextures = new List<Texture2D>(); public EmbeddedPngSpriteProvider(IEmbeddedResourceStreamProvider resources, ILogger log) { _resources = resources; _log = log; _cache[ThreatResponseHint.Block] = Load("Valheim.Foresight.Assets.Icons.block_icon.png"); _cache[ThreatResponseHint.Parry] = Load("Valheim.Foresight.Assets.Icons.parry_icon.png"); _cache[ThreatResponseHint.Dodge] = Load("Valheim.Foresight.Assets.Icons.roll_icon.png"); _cache[ThreatResponseHint.None] = null; } public Sprite? GetIcon(ThreatResponseHint hint) { if (!_cache.TryGetValue(hint, out Sprite value)) { return null; } return value; } private Sprite? Load(string resourceName) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) using Stream stream = _resources.Open(resourceName); if (stream == null) { _log.LogWarning("Embedded sprite not found: " + resourceName); return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); byte[] array = memoryStream.ToArray(); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); ImageConversion.LoadImage(val, array); _createdTextures.Add(val); Rect val2 = new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height); Sprite val3 = Sprite.Create(val, val2, new Vector2(0.5f, 0.5f), 100f); _createdSprites.Add(val3); return val3; } public void Dispose() { foreach (Sprite createdSprite in _createdSprites) { if ((Object)(object)createdSprite != (Object)null) { Object.Destroy((Object)(object)createdSprite); } } _createdSprites.Clear(); foreach (Texture2D createdTexture in _createdTextures) { if ((Object)(object)createdTexture != (Object)null) { Object.Destroy((Object)(object)createdTexture); } } _createdTextures.Clear(); } } public static class AttackNameFormatter { private static readonly HashSet<string> ForbiddenWords = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "idle" }; public static string GetDisplayName(Attack? attack, string? animationName) { return FormatAttackName(animationName) ?? FormatAttackName(attack?.m_attackAnimation) ?? "Attack"; } private static string? FormatAttackName(string? rawName) { if (string.IsNullOrWhiteSpace(rawName)) { return null; } string sourceName = rawName.Trim(); if (ForbiddenWords.Any((string word) => sourceName.Contains(word, StringComparison.OrdinalIgnoreCase))) { return null; } string text = sourceName.Replace("attack", "Attack").Replace("_", " ").Trim() .ToLowerInvariant(); if (string.IsNullOrEmpty(text)) { return null; } string text2 = string.Concat(text.Where((char c) => !char.IsDigit(c))).Trim(); if (string.IsNullOrEmpty(text2)) { return null; } return text2; } } public sealed class UnityCastbarRenderer : IUnityCastbarRenderer, IDisposable { private struct CastbarComponents { public Image? FillImage { get; set; } public GameObject? ParryIndicator { get; set; } public RectTransform? ParryIndicatorRect { get; set; } public Image? ParryIndicatorImage { get; set; } public TextMeshProUGUI? AttackNameText { get; set; } public TextMeshProUGUI? TimerText { get; set; } } private const string CastbarObjectName = "Foresight_Castbar"; private const string FillName = "Castbar_Fill"; private const string ParryIndicatorName = "Castbar_ParryIndicator"; private const string AttackNameTextName = "Castbar_AttackName"; private const string TimerTextName = "Castbar_Timer"; private const float BorderThickness = 3f; private const float OffestLevelFactorY = -11f; private TMP_FontAsset? _cachedFont; private readonly List<Texture2D> _createdTextures = new List<Texture2D>(); private readonly List<Sprite> _createdSprites = new List<Sprite>(); private readonly List<GameObject>? _createdCastbars = new List<GameObject>(); private Sprite? _cachedParryActiveSprite; private Sprite? _cachedParryIndicatorSprite; private readonly ILogger _logger; private readonly IForesightConfiguration _config; private readonly IParryWindowService _parryWindowService; public UnityCastbarRenderer(ILogger logger, IForesightConfiguration config, IParryWindowService parryWindowService) { _logger = logger; _config = config ?? throw new ArgumentNullException("config"); _parryWindowService = parryWindowService ?? throw new ArgumentNullException("parryWindowService"); _config.SettingsChanged += OnConfigurationChanged; } public void RenderCastbar(Transform hudParent, ActiveAttackInfo? attackInfo, Character? character = null) { IForesightConfiguration foresightConfig = ValheimForesightPlugin.ForesightConfig; if (foresightConfig == null || !foresightConfig.AttackCastbarEnabled.Value) { Transform val = hudParent.Find("Foresight_Castbar"); if (val != null) { ((Component)val).gameObject.SetActive(false); } return; } bool flag = foresightConfig.DebugEnabled.Value || foresightConfig.AlwaysDisplayCastbar.Value || (attackInfo != null && !attackInfo.IsExpired); bool isBoss = (Object)(object)character != (Object)null && character.IsBoss(); GameObject orCreateCastbarObject = GetOrCreateCastbarObject(hudParent, isBoss, character); orCreateCastbarObject.SetActive(flag); if (flag) { UpdateCastbarSize(orCreateCastbarObject, isBoss, character); UpdateCastbarProgress(orCreateCastbarObject, attackInfo); } } private GameObject GetOrCreateCastbarObject(Transform hudParent, bool isBoss, Character? character = null) { Transform val = hudParent.Find("Foresight_Castbar"); if (val != null) { return ((Component)val).gameObject; } return CreateCastbarObject(hudParent, isBoss, character); } private GameObject CreateCastbarObject(Transform hudParent, bool isBoss, Character? character = null) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) IForesightConfiguration foresightConfig = ValheimForesightPlugin.ForesightConfig; GameObject val = new GameObject("Foresight_Castbar"); val.transform.SetParent(hudParent, false); RectTransform val2 = val.AddComponent<RectTransform>(); val2.anchorMin = new Vector2(0.5f, 0f); val2.anchorMax = new Vector2(0.5f, 0f); val2.pivot = new Vector2(0.5f, 1f); if (foresightConfig != null) { if (isBoss) { val2.anchoredPosition = new Vector2(foresightConfig.BossCastbarOffsetX.Value, foresightConfig.BossCastbarOffsetY.Value); val2.sizeDelta = new Vector2(foresightConfig.BossCastbarWidth.Value, foresightConfig.BossCastbarHeight.Value); } else { float num = foresightConfig.AttackCastbarOffsetY.Value; if ((Object)(object)character != (Object)null && character.GetLevel() > 1) { num += -11f; } val2.anchoredPosition = new Vector2(foresightConfig.AttackCastbarOffsetX.Value, num); val2.sizeDelta = new Vector2(foresightConfig.AttackCastbarWidth.Value, foresightConfig.AttackCastbarHeight.Value); } } else { val2.anchoredPosition = new Vector2(0f, -10f); val2.sizeDelta = new Vector2(100f, 16f); } CreateBackground(val.transform); CreateFill(val.transform); CreateParryIndicator(val.transform); CreateAttackNameText(val.transform); CreateTimerText(val.transform); _createdCastbars.Add(val); return val; } private void CreateFill(Transform parent) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Castbar_Fill"); val.transform.SetParent(parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0f, 0f); obj.anchorMax = new Vector2(0f, 1f); obj.pivot = new Vector2(0f, 0.5f); obj.anchoredPosition = new Vector2(3f, 0f); float num = (ValheimForesightPlugin.ForesightConfig?.AttackCastbarWidth.Value ?? 100f) - 6f; float num2 = -6f; obj.sizeDelta = new Vector2(num, num2); Image obj2 = val.AddComponent<Image>(); Color value = _config.CastbarFillColor.Value; obj2.sprite = CreateGradientSprite(value); ((Graphic)obj2).color = Color.white; obj2.type = (Type)3; obj2.fillMethod = (FillMethod)0; obj2.fillOrigin = 0; obj2.fillAmount = 0f; ((Graphic)obj2).raycastTarget = false; } private Sprite CreateWhiteSprite() { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, Color.white); val.Apply(); _createdTextures.Add(val); Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f), 1f); _createdSprites.Add(val2); return val2; } private Sprite CreateGradientSprite(Color baseColor) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 16, (TextureFormat)4, false); Color val2 = default(Color); for (int i = 0; i < 16; i++) { float num = (float)i / 15f; float num2 = Mathf.Lerp(0.65f, 1f, num); ((Color)(ref val2))..ctor(baseColor.r * num2, baseColor.g * num2, baseColor.b * num2, baseColor.a); val.SetPixel(0, i, val2); } val.Apply(); _createdTextures.Add(val); Sprite val3 = Sprite.Create(val, new Rect(0f, 0f, 1f, 16f), new Vector2(0.5f, 0.5f), 1f); _createdSprites.Add(val3); return val3; } private Sprite CreateHollowBorderSprite() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) int num = 16; int num2 = 1; Texture2D val = new Texture2D(num, num, (TextureFormat)4, false); Color value = _config.CastbarBorderColor.Value; Color val2 = default(Color); ((Color)(ref val2))..ctor(0f, 0f, 0f, 0f); for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { bool flag = i < num2 || i >= num - num2 || j < num2 || j >= num - num2; val.SetPixel(i, j, flag ? value : val2); } } val.Apply(); _createdTextures.Add(val); Sprite val3 = Sprite.Create(val, new Rect(0f, 0f, (float)num, (float)num), new Vector2(0.5f, 0.5f), 1f, 0u, (SpriteMeshType)0, new Vector4((float)num2, (float)num2, (float)num2, (float)num2)); _createdSprites.Add(val3); return val3; } private void CreateBackground(Transform parent) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Castbar_Border"); val.transform.SetParent(parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.sizeDelta = Vector2.zero; Image obj2 = val.AddComponent<Image>(); obj2.sprite = CreateHollowBorderSprite(); ((Graphic)obj2).color = Color.white; ((Graphic)obj2).raycastTarget = false; obj2.type = (Type)1; GameObject val2 = new GameObject("Castbar_Background"); val2.transform.SetParent(parent, false); RectTransform obj3 = val2.AddComponent<RectTransform>(); obj3.anchorMin = Vector2.zero; obj3.anchorMax = Vector2.one; obj3.offsetMin = new Vector2(3f, 3f); obj3.offsetMax = new Vector2(-3f, -3f); Image obj4 = val2.AddComponent<Image>(); obj4.sprite = CreateWhiteSprite(); Color value = _config.CastbarBackgroundColor.Value; ((Graphic)obj4).color = value; ((Graphic)obj4).raycastTarget = false; } private void CreateParryIndicator(Transform parent) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Castbar_ParryIndicator"); val.transform.SetParent(parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.offsetMin = Vector2.zero; obj.offsetMax = Vector2.zero; obj.sizeDelta = Vector2.zero; Image obj2 = val.AddComponent<Image>(); Color value = _config.CastbarParryIndicatorColor.Value; obj2.sprite = CreateGradientSprite(value); ((Graphic)obj2).color = Color.white; ((Graphic)obj2).raycastTarget = false; val.SetActive(false); } private void CreateAttackNameText(Transform parent) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Castbar_AttackName"); val.transform.SetParent(parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0f, 0f); obj.anchorMax = new Vector2(0.6f, 1f); obj.sizeDelta = Vector2.zero; obj.anchoredPosition = Vector2.zero; TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>(); TMP_FontAsset valheimFont = GetValheimFont(); if (valheimFont != null) { ((TMP_Text)val2).font = valheimFont; } ((TMP_Text)val2).text = string.Empty; ((TMP_Text)val2).fontSize = 12f; ((TMP_Text)val2).fontSizeMin = 6f; ((TMP_Text)val2).fontSizeMax = 14f; ((TMP_Text)val2).enableAutoSizing = true; Color value = _config.CastbarTextColor.Value; ((Graphic)val2).color = value; ((TMP_Text)val2).alignment = (TextAlignmentOptions)4097; ((TMP_Text)val2).margin = new Vector4(4f, 0f, 2f, 0f); ((TMP_Text)val2).fontStyle = (FontStyles)0; ((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)val2).overflowMode = (TextOverflowModes)1; ((Graphic)val2).raycastTarget = false; ((Behaviour)val2).enabled = _config.AttackCastbarTextEnabled.Value; Shadow obj2 = val.AddComponent<Shadow>(); Color value2 = _config.CastbarTextShadowColor.Value; obj2.effectColor = value2; obj2.effectDistance = new Vector2(1f, -1f); } private void CreateTimerText(Transform parent) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Castbar_Timer"); val.transform.SetParent(parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0.6f, 0f); obj.anchorMax = new Vector2(1f, 1f); obj.sizeDelta = Vector2.zero; obj.anchoredPosition = Vector2.zero; TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>(); TMP_FontAsset valheimFont = GetValheimFont(); if (valheimFont != null) { ((TMP_Text)val2).font = valheimFont; } ((TMP_Text)val2).text = string.Empty; ((TMP_Text)val2).fontSize = 12f; ((TMP_Text)val2).fontSizeMin = 6f; ((TMP_Text)val2).fontSizeMax = 14f; ((TMP_Text)val2).enableAutoSizing = true; Color value = _config.CastbarTextColor.Value; ((Graphic)val2).color = value; ((TMP_Text)val2).alignment = (TextAlignmentOptions)4100; ((TMP_Text)val2).margin = new Vector4(2f, 0f, 4f, 0f); ((TMP_Text)val2).fontStyle = (FontStyles)0; ((Graphic)val2).raycastTarget = false; ((Behaviour)val2).enabled = _config.AttackCastbarTextEnabled.Value; Shadow obj2 = val.AddComponent<Shadow>(); Color value2 = _config.CastbarTextShadowColor.Value; obj2.effectColor = value2; obj2.effectDistance = new Vector2(1f, -1f); } private void UpdateCastbarSize(GameObject castbarObject, bool isBoss, Character? character = null) { //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) float num = (isBoss ? _config.BossCastbarWidth.Value : _config.AttackCastbarWidth.Value); float num2 = (isBoss ? _config.BossCastbarHeight.Value : _config.AttackCastbarHeight.Value); RectTransform component = castbarObject.GetComponent<RectTransform>(); if (component != null) { float num3 = (isBoss ? _config.BossCastbarOffsetX.Value : _config.AttackCastbarOffsetX.Value); float num4 = (isBoss ? _config.BossCastbarOffsetY.Value : _config.AttackCastbarOffsetY.Value); if (!isBoss && (Object)(object)character != (Object)null && character.GetLevel() > 1) { num4 += -11f; } component.anchoredPosition = new Vector2(num3, num4); component.sizeDelta = new Vector2(num, num2); } Transform val = castbarObject.transform.Find("Castbar_Fill"); if (val != null) { RectTransform component2 = ((Component)val).GetComponent<RectTransform>(); if (component2 != null) { float num5 = num - 6f; component2.sizeDelta = new Vector2(num5, -6f); } } UpdateTextSizes(castbarObject, num, num2); } private void UpdateTextSizes(GameObject castbarObject, float castbarWidth, float castbarHeight) { Transform val = castbarObject.transform.Find("Castbar_AttackName"); Transform val2 = castbarObject.transform.Find("Castbar_Timer"); float num = Mathf.Clamp(castbarHeight * 0.7f, 8f, 16f); float fontSizeMin = Mathf.Max(6f, num * 0.5f); float fontSizeMax = Mathf.Min(18f, num * 1.2f); if (castbarWidth < 80f) { num = Mathf.Clamp(castbarHeight * 0.5f, 6f, 10f); fontSizeMin = 6f; fontSizeMax = 10f; } if (val != null) { TextMeshProUGUI component = ((Component)val).GetComponent<TextMeshProUGUI>(); if (component != null) { ((TMP_Text)component).fontSize = num; ((TMP_Text)component).fontSizeMin = fontSizeMin; ((TMP_Text)component).fontSizeMax = fontSizeMax; if (castbarWidth < 50f) { ((Behaviour)component).enabled = false; } else { ((Behaviour)component).enabled = true; } } } if (val2 == null) { return; } TextMeshProUGUI component2 = ((Component)val2).GetComponent<TextMeshProUGUI>(); if (component2 != null) { ((TMP_Text)component2).fontSize = num; ((TMP_Text)component2).fontSizeMin = fontSizeMin; ((TMP_Text)component2).fontSizeMax = fontSizeMax; if (castbarWidth < 30f) { ((Behaviour)component2).enabled = false; } else { ((Behaviour)component2).enabled = true; } } } private void UpdateCastbarProgress(GameObject castbarObject, ActiveAttackInfo? attackInfo) { CastbarComponents castbarComponents = GetCastbarComponents(castbarObject); if (castbarComponents.FillImage != null) { if (attackInfo == null || attackInfo.IsExpired) { ResetCastbarDisplay(castbarComponents); } else { UpdateActiveAttackDisplay(castbarComponents, attackInfo); } } } private CastbarComponents GetCastbarComponents(GameObject castbarObject) { Transform val = castbarObject.transform.Find("Castbar_Fill"); Transform val2 = castbarObject.transform.Find("Castbar_ParryIndicator"); Transform val3 = castbarObject.transform.Find("Castbar_AttackName"); Transform val4 = castbarObject.transform.Find("Castbar_Timer"); CastbarComponents result = default(CastbarComponents); result.FillImage = ((val != null) ? ((Component)val).GetComponent<Image>() : null); result.ParryIndicator = ((val2 != null) ? ((Component)val2).gameObject : null); result.ParryIndicatorRect = ((val2 != null) ? ((Component)val2).GetComponent<RectTransform>() : null); result.ParryIndicatorImage = ((val2 != null) ? ((Component)val2).GetComponent<Image>() : null); result.AttackNameText = ((val3 != null) ? ((Component)val3).GetComponent<TextMeshProUGUI>() : null); result.TimerText = ((val4 != null) ? ((Component)val4).GetComponent<TextMeshProUGUI>() : null); return result; } private void ResetCastbarDisplay(CastbarComponents components) { if (components.FillImage != null) { components.FillImage.fillAmount = 0f; } if (components.ParryIndicator != null) { components.ParryIndicator.SetActive(false); } if (components.AttackNameText != null) { ((TMP_Text)components.AttackNameText).text = string.Empty; } if (components.TimerText != null) { ((TMP_Text)components.TimerText).text = string.Empty; } } private void UpdateActiveAttackDisplay(CastbarComponents components, ActiveAttackInfo attackInfo) { components.FillImage.fillAmount = attackInfo.Progress; UpdateAttackText(components, attackInfo); (float, float)? parryWindowInfo = _parryWindowService.GetParryWindowInfo(attackInfo, attackInfo.Duration); if (!parryWindowInfo.HasValue) { ApplyNoParryWindowStyle(components); return; } UpdateParryIndicatorPosition(components, parryWindowInfo.Value); ApplyParryWindowStyle(components, attackInfo); } private void UpdateAttackText(CastbarComponents components, ActiveAttackInfo attackInfo) { bool value = _config.AttackCastbarTextEnabled.Value; if (components.AttackNameText != null) { ((TMP_Text)components.AttackNameText).text = (value ? attackInfo.AttackName : string.Empty); ((Behaviour)components.AttackNameText).enabled = value; } if (components.TimerText != null) { ((TMP_Text)components.TimerText).text = (value ? $"{attackInfo.TimeRemaining:F1}s" : string.Empty); ((Behaviour)components.TimerText).enabled = value; } } private void ApplyNoParryWindowStyle(CastbarComponents components) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (components.ParryIndicator != null) { components.ParryIndicator.SetActive(false); } if (components.FillImage != null) { ((Graphic)components.FillImage).color = Color.white; } Color value = _config.CastbarTextColor.Value; if (components.AttackNameText != null) { ((Graphic)components.AttackNameText).color = value; } if (components.TimerText != null) { ((Graphic)components.TimerText).color = value; } } private void UpdateParryIndicatorPosition(CastbarComponents components, (float startPos, float windowWidth) parryWindowInfo) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (components.ParryIndicatorRect != null) { var (num, num2) = parryWindowInfo; components.ParryIndicatorRect.anchorMin = new Vector2(num, 0f); components.ParryIndicatorRect.anchorMax = new Vector2(num + num2, 1f); components.ParryIndicatorRect.offsetMin = new Vector2(0f, 3f); components.ParryIndicatorRect.offsetMax = new Vector2(0f, -3f); } } private void ApplyParryWindowStyle(CastbarComponents components, ActiveAttackInfo attackInfo) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) bool isInParryWindow = _parryWindowService.IsInParryWindow(attackInfo); if (components.FillImage != null) { ((Graphic)components.FillImage).color = Color.white; } Color value = _config.CastbarTextColor.Value; if (components.AttackNameText != null) { ((Graphic)components.AttackNameText).color = value; } if (components.TimerText != null) { ((Graphic)components.TimerText).color = value; } UpdateParryIndicatorVisual(components, isInParryWindow); } private void UpdateParryIndicatorVisual(CastbarComponents components, bool isInParryWindow) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (components.ParryIndicator == null) { return; } components.ParryIndicator.SetActive(true); if (components.ParryIndicatorImage == null) { return; } if (isInParryWindow) { if (_cachedParryActiveSprite == null) { Color value = _config.CastbarParryActiveColor.Value; _cachedParryActiveSprite = CreateGradientSprite(value); } components.ParryIndicatorImage.sprite = _cachedParryActiveSprite; } else { if (_cachedParryIndicatorSprite == null) { Color value2 = _config.CastbarParryIndicatorColor.Value; _cachedParryIndicatorSprite = CreateGradientSprite(value2); } components.ParryIndicatorImage.sprite = _cachedParryIndicatorSprite; } ((Graphic)components.ParryIndicatorImage).color = Color.white; } private TMP_FontAsset? GetValheimFont() { if (_cachedFont != null) { return _cachedFont; } try { EnemyHud instance = EnemyHud.instance; if (instance != null) { TextMeshProUGUI[] componentsInChildren = ((Component)instance).GetComponentsInChildren<TextMeshProUGUI>(true); if (componentsInChildren.Length != 0) { _cachedFont = ((TMP_Text)componentsInChildren[0]).font; ILogger log = ValheimForesightPlugin.Log; TMP_FontAsset? cachedFont = _cachedFont; log.LogInfo("Found and cached Valheim font: " + ((cachedFont != null) ? ((Object)cachedFont).name : null)); return _cachedFont; } } TextMeshProUGUI[] array = Object.FindObjectsByType<TextMeshProUGUI>((FindObjectsSortMode)0); if (array.Length != 0) { _cachedFont = ((TMP_Text)array[0]).font; ILogger log2 = ValheimForesightPlugin.Log; TMP_FontAsset? cachedFont2 = _cachedFont; log2.LogInfo("Found and cached font from scene: " + ((cachedFont2 != null) ? ((Object)cachedFont2).name : null)); return _cachedFont; } ValheimForesightPlugin.Log.LogWarning("Could not find Valheim font!"); } catch (Exception ex) { ValheimForesightPlugin.Log.LogError("Error getting Valheim font: " + ex.Message); } return null; } private void OnConfigurationChanged(object? sender, EventArgs e) { InvalidateColorCache(); UpdateExistingCastbarColors(); } private void InvalidateColorCache() { if (_cachedParryActiveSprite != null) { _createdSprites.Remove(_cachedParryActiveSprite); Object.Destroy((Object)(object)_cachedParryActiveSprite); _cachedParryActiveSprite = null; } if (_cachedParryIndicatorSprite != null) { _createdSprites.Remove(_cachedParryIndicatorSprite); Object.Destroy((Object)(object)_cachedParryIndicatorSprite); _cachedParryIndicatorSprite = null; } } private void UpdateExistingCastbarColors() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) if (_createdCastbars == null) { return; } foreach (GameObject createdCastbar in _createdCastbars) { if (createdCastbar == null || (Object)(object)createdCastbar == (Object)null) { continue; } Transform val = createdCastbar.transform.Find("Castbar_Fill"); if (val != null) { Image component = ((Component)val).GetComponent<Image>(); if (component != null) { if (component.sprite != null) { _createdSprites.Remove(component.sprite); Object.Destroy((Object)(object)component.sprite); } Color value = _config.CastbarFillColor.Value; component.sprite = CreateGradientSprite(value); ((Graphic)component).color = Color.white; } } Transform val2 = createdCastbar.transform.Find("Castbar_Border"); if (val2 != null) { Image component2 = ((Component)val2).GetComponent<Image>(); if (component2 != null) { if (component2.sprite != null) { _createdSprites.Remove(component2.sprite); Object.Destroy((Object)(object)component2.sprite); } component2.sprite = CreateHollowBorderSprite(); ((Graphic)component2).color = Color.white; } } Transform val3 = createdCastbar.transform.Find("Castbar_Background"); if (val3 != null) { Image component3 = ((Component)val3).GetComponent<Image>(); if (component3 != null) { Color value2 = _config.CastbarBackgroundColor.Value; ((Graphic)component3).color = value2; } } Transform val4 = createdCastbar.transform.Find("Castbar_ParryIndicator"); if (val4 != null) { Image component4 = ((Component)val4).GetComponent<Image>(); if (component4 != null) { if (component4.sprite != null) { _createdSprites.Remove(component4.sprite); Object.Destroy((Object)(object)component4.sprite); } Color value3 = _config.CastbarParryIndicatorColor.Value; component4.sprite = CreateGradientSprite(value3); ((Graphic)component4).color = Color.white; } } Transform val5 = createdCastbar.transform.Find("Castbar_AttackName"); if (val5 != null) { TextMeshProUGUI component5 = ((Component)val5).GetComponent<TextMeshProUGUI>(); if (component5 != null) { Color value4 = _config.CastbarTextColor.Value; ((Graphic)component5).color = value4; } Shadow component6 = ((Component)val5).GetComponent<Shadow>(); if (component6 != null) { Color value5 = _config.CastbarTextShadowColor.Value; component6.effectColor = value5; } } Transform val6 = createdCastbar.transform.Find("Castbar_Timer"); if (val6 != null) { TextMeshProUGUI component7 = ((Component)val6).GetComponent<TextMeshProUGUI>(); if (component7 != null) { Color value6 = _config.CastbarTextColor.Value; ((Graphic)component7).color = value6; } Shadow component8 = ((Component)val6).GetComponent<Shadow>(); if (component8 != null) { Color value7 = _config.CastbarTextShadowColor.Value; component8.effectColor = value7; } } } } public void Dispose() { _config.SettingsChanged -= OnConfigurationChanged; CleanupCastbars(); foreach (Sprite createdSprite in _createdSprites) { if (createdSprite != null) { Object.Destroy((Object)(object)createdSprite); } } _createdSprites.Clear(); foreach (Texture2D createdTexture in _createdTextures) { if (createdTexture != null) { Object.Destroy((Object)(object)createdTexture); } } _createdTextures.Clear(); _cachedFont = null; _cachedParryActiveSprite = null; _cachedParryIndicatorSprite = null; } private void CleanupCastbars() { try { if (_createdCastbars == null) { return; } foreach (GameObject createdCastbar in _createdCastbars) { if ((Object)(object)createdCastbar != (Object)null && !((object)createdCastbar).Equals((object?)null)) { _logger?.LogDebug("[CleanupCastbars] Destroying castbar: " + ((Object)createdCastbar).name); Object.Destroy((Object)(object)createdCastbar); } } _logger?.LogInfo($"Cleaned up {_createdCastbars.Count} castbar(s)"); _createdCastbars?.Clear(); } catch (Exception ex) { _logger?.LogError("[CleanupCastbars] Error cleaning up castbars: " + ex.Message); } } } public sealed class UnityResourcesThreatIconSpriteProvider : IThreatIconSpriteProvider, IDisposable { private readonly IResourcesWrapper _resources; private readonly ILogger _logger; private readonly Dictionary<ThreatResponseHint, Sprite?> _cache = new Dictionary<ThreatResponseHint, Sprite>(); private const string BlockResource = "Valheim.Foresight.Assets.Icons.block_icon.png"; private const string ParryResource = "Valheim.Foresight.Assets.Icons.parry_icon.png"; private const string DodgeResource = "Valheim.Foresight.Assets.Icons.roll_icon.png"; public UnityResourcesThreatIconSpriteProvider(IResourcesWrapper resources, ILogger logger) { _resources = resources; _logger = logger; Preload(); } private void Preload() { _cache[ThreatResponseHint.Block] = LoadSprite("Valheim.Foresight.Assets.Icons.block_icon.png"); _cache[ThreatResponseHint.Parry] = LoadSprite("Valheim.Foresight.Assets.Icons.parry_icon.png"); _cache[ThreatResponseHint.Dodge] = LoadSprite("Valheim.Foresight.Assets.Icons.roll_icon.png"); _cache[ThreatResponseHint.None] = null; _logger.LogDebug("[Preload] Preload completed. " + $"Block={_cache[ThreatResponseHint.Block] != null}, " + $"Parry={_cache[ThreatResponseHint.Parry] != null}, " + $"Dodge={_cache[ThreatResponseHint.Dodge] != null}"); } private Sprite? LoadSprite(string path) { Sprite? obj = _resources.LoadSprite(path); if ((Object)(object)obj == (Object)null) { _logger.LogWarning("Failed to load threat icon sprite at '" + path + "'."); } return obj; } public Sprite? GetIcon(ThreatResponseHint hint) { if (!_cache.TryGetValue(hint, out Sprite value)) { _logger.LogWarning($"No cached sprite entry for hint={hint}."); return null; } _logger.LogDebug(string.Format("[{0}] {1}(hint={2}) -> spriteNull={3}", "GetIcon", "GetIcon", hint, value == null)); return value; } public void Dispose() { } } public sealed class UnityThreatHudIconRenderer : IThreatHudIconRenderer, IDisposable { private const string IconObjectName = "Foresight_ThreatIcon"; private readonly IThreatIconSpriteProvider _spriteProvider; private readonly List<GameObject> _createdIcons = new List<GameObject>(); private bool _disposed; public UnityThreatHudIconRenderer(IThreatIconSpriteProvider spriteProvider) { _spriteProvider = spriteProvider; } public void RenderIcon(TextMeshProUGUI? nameLabel, ThreatResponseHint hint) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) if (nameLabel == null) { return; } IForesightConfiguration foresightConfig = ValheimForesightPlugin.ForesightConfig; if (foresightConfig == null || !foresightConfig.ThreatIconEnabled.Value) { GameObject existingIconObject = GetExistingIconObject(((TMP_Text)nameLabel).transform); if ((Object)(object)existingIconObject != (Object)null) { existingIconObject.SetActive(false); } return; } _ = ((TMP_Text)nameLabel).text; GameObject orCreateIconObject = GetOrCreateIconObject(((TMP_Text)nameLabel).transform); Image val = orCreateIconObject.GetComponent<Image>() ?? orCreateIconObject.AddComponent<Image>(); Sprite icon = _spriteProvider.GetIcon(hint); bool flag = hint != 0 && icon != null; orCreateIconObject.SetActive(flag); if (!flag) { if (hint != 0 && icon == null) { ValheimForesightPlugin.Log?.LogWarning($"Sprite is null for hint={hint}, hiding icon. " + "Check ThreatIconSpriteProvider paths."); } return; } val.sprite = icon; val.preserveAspect = true; RectTransform component = orCreateIconObject.GetComponent<RectTransform>(); if ((Object)(object)component != (Object)null) { component.anchoredPosition = new Vector2(foresightConfig.ThreatIconOffsetX.Value, foresightConfig.ThreatIconOffsetY.Value); component.sizeDelta = new Vector2(foresightConfig.ThreatIconSize.Value, foresightConfig.ThreatIconSize.Value); } } private GameObject? GetExistingIconObject(Transform nameTransform) { Transform obj = (nameTransform.parent ?? nameTransform).Find("Foresight_ThreatIcon"); if (obj == null) { return null; } return ((Component)obj).gameObject; } private GameObject GetOrCreateIconObject(Transform nameTransform) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) GameObject existingIconObject = GetExistingIconObject(nameTransform); if ((Object)(object)existingIconObject != (Object)null) { return existingIconObject; } Transform val = nameTransform.parent ?? nameTransform; ValheimForesightPlugin.Log?.LogDebug("[GetOrCreateIconObject] Creating new icon object 'Foresight_ThreatIcon' under parent '" + ((Object)val).name + "'."); GameObject val2 = new GameObject("Foresight_ThreatIcon"); val2.transform.SetParent(val, false); _createdIcons.Add(val2); RectTransform val3 = val2.AddComponent<RectTransform>(); val3.anchorMin = new Vector2(0f, 0.5f); val3.anchorMax = new Vector2(0f, 0.5f); val3.pivot = new Vector2(0f, 0.5f); IForesightConfiguration foresightConfig = ValheimForesightPlugin.ForesightConfig; if (foresightConfig != null) { val3.anchoredPosition = new Vector2(foresightConfig.ThreatIconOffsetX.Value, foresightConfig.ThreatIconOffsetY.Value); val3.sizeDelta = new Vector2(foresightConfig.ThreatIconSize.Value, foresightConfig.ThreatIconSize.Value); } return val2; } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { _spriteProvider?.Dispose(); } foreach (GameObject createdIcon in _createdIcons) { if ((Object)(object)createdIcon != (Object)null) { Object.Destroy((Object)(object)createdIcon); } } _createdIcons.Clear(); _disposed = true; } ~UnityThreatHudIconRenderer() { Dispose(disposing: false); } } } namespace Valheim.Foresight.Services.Hud.Wrappers { public sealed class ResourcesWrapper : IResourcesWrapper { public Sprite? LoadSprite(string path) { return Resources.Load<Sprite>(path); } } } namespace Valheim.Foresight.Services.Hud.Interfaces { public interface IEmbeddedResourceStreamProvider { Stream? Open(string resourceName); string[] GetNames(); } public interface IResourcesWrapper { Sprite? LoadSprite(string path); } public interface IThreatHudIconRenderer : IDisposable { void RenderIcon(TextMeshProUGUI? hud, ThreatResponseHint hint); } public interface IThreatIconSpriteProvider : IDisposable { Sprite? GetIcon(ThreatResponseHint hint); } public interface IUnityCastbarRenderer : IDisposable { void RenderCastbar(Transform hudParent, ActiveAttackInfo? attackInfo, Character? character = null); } } namespace Valheim.Foresight.Services.Damage { public sealed class BlockDamageEstimator : DamageEstimatorBase { private const float BlockingSkillPercentPerLevel = 0.005f; public BlockDamageEstimator(ILogger logger) : base(logger) { } protected override float ApplyActiveDefense(PlayerDefenseStats defenseStats, float physicalDamage) { if (physicalDamage <= 0f) { return 0f; } if (defenseStats.Shield == null) { Logger.LogDebug("[BlockDamageEstimator] no shield."); return physicalDamage; } float num = CalculateEffectiveBlockPower(defenseStats); float num2 = Mathf.Min(physicalDamage, num); float num3 = physicalDamage - num2; Logger.LogDebug(string.Format("[{0}] Block: raw={1:F1}, ", "BlockDamageEstimator", physicalDamage) + $"baseBlock={defenseStats.Shield.m_shared.m_blockPower:F1}, " + $"skill={defenseStats.BlockingSkillLevel:F0}, effBlock={num:F1}, " + $"blocked={num2:F1}, remain={num3:F1}"); return num3; } private float CalculateEffectiveBlockPower(PlayerDefenseStats defenseStats) { if (defenseStats.Shield == null) { return 0f; } float blockPower = defenseStats.Shield.m_shared.m_blockPower; float num = 1f + 0.005f * defenseStats.BlockingSkillLevel; return blockPower * num; } } public abstract class DamageEstimatorBase : IDamageEstimator { private const float MinimumDamage = 1f; protected readonly ILogger Logger; protected DamageEstimatorBase(ILogger logger) { Logger = logger ?? throw new ArgumentNullException("logger"); } public float EstimateEffectiveDamage(Player player, float rawDamage) { if ((Object)(object)player == (Object)null || rawDamage <= 0f) { return 0f; } PlayerDefenseStats defenseStats = PlayerDefenseStats.FromPlayer(player); return EstimateEffectiveDamage(defenseStats, rawDamage); } public float EstimateEffectiveDamage(PlayerDefenseStats defenseStats, float rawDamage) { if (rawDamage <= 0f) { return 0f; } float physicalDamage = ApplyActiveDefense(defenseStats, rawDamage); float physicalAfterArmor = ApplyArmor(defenseStats.Armor, physicalDamage); float num = ApplyResistances(physicalAfterArmor, 0f); return Mathf.Max(1f, num); } protected abstract float ApplyActiveDefense(PlayerDefenseStats defenseStats, float physicalDamage); private float ApplyArmor(float armor, float physicalDamage) { if (physicalDamage <= 0f) { return 0f; } float num = ((armor <= 0f) ? physicalDamage : ((!(armor < physicalDamage / 2f)) ? (physicalDamage * physicalDamage / (4f * armor)) : (physicalDamage - armor))); num = Mathf.Max(1f, num); Logger.LogDebug(string.Format("[{0}] Armor: in={1:F1}, armor={2:F1}, out={3:F1}", "ApplyArmor", physicalDamage, armor, num)); return num; } private float ApplyResistances(float physicalAfterArmor, float elementalDamage) { float num = physicalAfterArmor + elementalDamage; Logger.LogDebug(string.Format("[{0}] Resists: phys={1:F1}, elem={2:F1}, total={3:F1}", "ApplyResistances", physicalAfterArmor, elementalDamage, num)); return num; } } public interface IDamageEstimator { float EstimateEffectiveDamage(Player player, float rawDamage); float EstimateEffectiveDamage(PlayerDefenseStats defenseStats, float rawDamage); } public sealed class ParryDamageEstimator : DamageEstimatorBase { private const float BlockingSkillPercentPerLevel = 0.005f; public ParryDamageEstimator(ILogger logger) : base(logger) { } protected override float ApplyActiveDefense(PlayerDefenseStats defenseStats, float physicalDamage) { if (physicalDamage <= 0f) { return 0f; } if (defenseStats.Shield == null) { Logger.LogDebug("[ApplyActiveDefense] no shield."); return physicalDamage; } float num = CalculateEffectiveParryPower(defenseStats); float num2 = Mathf.Min(physicalDamage, num); float num3 = physicalDamage - num2; Logger.LogDebug(string.Format("[{0}] Parry: raw={1:F1}, ", "ParryDamageEstimator", physicalDamage) + $"baseBlock={defenseStats.Shield.m_shared.m_blockPower:F1}, " + $"parryBonus={defenseStats.Shield.m_shared.m_timedBlockBonus:F1}, " + $"skill={defenseStats.BlockingSkillLevel:F0}, " + $"effBlock={num:F1}, blocked={num2:F1}, remain={num3:F1}"); return num3; } private float CalculateEffectiveParryPower(PlayerDefenseStats defenseStats) { if (defenseStats.Shield == null) { return 0f; } SharedData shared = defenseStats.Shield.m_shared; float blockPower = shared.m_blockPower; float timedBlockBonus = shared.m_timedBlockBonus; float num = 1f + 0.005f * defenseStats.BlockingSkillLevel; return (blockPower + timedBlockBonus) * num; } } } namespace Valheim.Foresight.Services.Combat { public sealed class CreatureAttackInspector : ICreatureAttackInspector { private readonly IZNetSceneWrapper _zNetSceneWrapper; private readonly ILogger _logger; public CreatureAttackInspector(IZNetSceneWrapper zNetSceneWrapper, ILogger logger) { _zNetSceneWrapper = zNetSceneWrapper ?? throw new ArgumentNullException("zNetSceneWrapper"); _logger = logger ?? throw new ArgumentNullException("logger"); } public float GetMaxAttackByPrefabName(string prefabName) { if (string.IsNullOrEmpty(prefabName) || _zNetSceneWrapper == null) { return 0f; } GameObject prefab = _zNetSceneWrapper.GetPrefab(prefabName); if (!((Object)(object)prefab == (Object)null)) { return InspectPrefabForMaxDamage(prefab); } return 0f; } public float GetMaxAttackForCharacter(Character character) { if ((Object)(object)character == (Object)null) { return 0f; } return InspectPrefabForMaxDamage(((Component)character).gameObject); } private float InspectPrefabForMaxDamage(GameObject prefabRoot) { if ((Object)(object)prefabRoot == (Object)null) { return 0f; } float maxDamage = 0f; InspectHumanoidEquipment(prefabRoot, ref maxDamage); InspectItemDrops(prefabRoot, ref maxDamage); InspectAttackComponents(prefabRoot, ref maxDamage); InspectItemSets(prefabRoot, ref maxDamage); _logger.LogDebug(string.Format("[{0}] Max damage: {1}", "InspectPrefabForMaxDamage", maxDamage)); return maxDamage; } private void InspectHumanoidEquipment(GameObject prefabRoot, ref float maxDamage) { Humanoid componentInChildren = prefabRoot.GetComponentInChildren<Humanoid>(true); if (!((Object)(object)componentInChildren == (Object)null)) { FieldRef<Humanoid, ItemData?>? rightItemRef = HumanoidFieldRefs.RightItemRef; ItemData item = ((rightItemRef != null) ? rightItemRef.Invoke(componentInChildren) : null); FieldRef<Humanoid, ItemData?>? leftItemRef = HumanoidFieldRefs.LeftItemRef; ItemData item2 = ((leftItemRef != null) ? leftItemRef.Invoke(componentInChildren) : null); UpdateMaxFromItem(item, "RightItem", ref maxDamage); UpdateMaxFromItem(item2, "LeftItem", ref maxDamage); if ((Object)(object)componentInChildren.m_unarmedWeapon != (Object)null) { UpdateMaxFromItem(componentInChildren.m_unarmedWeapon.m_itemData, "UnarmedWeapon", ref maxDamage); } } } private void InspectItemDrops(GameObject prefabRoot, ref float maxDamage) { ItemDrop[] componentsInChildren = prefabRoot.GetComponentsInChildren<ItemDrop>(true); foreach (ItemDrop val in componentsInChildren) { UpdateMaxFromItem(val.m_itemData, ((Object)val).name, ref maxDamage); } } private void InspectAttackComponents(GameObject prefabRoot, ref float maxDamage) { Attack[] componentsInChildren = prefabRoot.GetComponentsInChildren<Attack>(true); foreach (Attack attack in componentsInChildren) { UpdateMaxFromAttack(attack, ref maxDamage); } } private void InspectItemSets(GameObject prefabRoot, ref float maxDamage) { Humanoid componentInChildren = prefabRoot.GetComponentInChildren<Humanoid>(true); if (componentInChildren?.m_randomSets == null || componentInChildren.m_randomSets.Length == 0) { return; } foreach (ItemDrop item in (from i in componentInChildren.m_randomSets.Where((ItemSet s) => s.m_items != null).SelectMany((ItemSet s) => s.m_items) where i != null select i).SelectMany((GameObject i) => i.GetComponentsInChildren<ItemDrop>(true))) { UpdateMaxFromItem(item.m_itemData, ((Object)item).name, ref maxDamage); } } private void UpdateMaxFromItem(ItemData? item, string source, ref float maxDamage) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (item != null) { float num = CalculateTotalDamage(item.m_shared.m_damages, source); if (num > maxDamage) { maxDamage = num; } } } private void UpdateMaxFromAttack(Attack? attack, ref float maxDamage) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (attack != null) { float num = 0f; FieldRef<Attack, ItemData?>? weaponRef = AttackFieldRefs.WeaponRef; ItemData val = ((weaponRef != null) ? weaponRef.Invoke(attack) : null); if (val != null) { num = CalculateTotalDamage(val.m_shared.m_damages, "Attack.Weapon"); } float num2 = ((attack.m_damageMultiplier > 0f) ? attack.m_damageMultiplier : 1f); float num3 = num * num2; if (num3 > maxDamage) { maxDamage = num3; } } } private float CalculateTotalDamage(DamageTypes dmg, string source) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) _logger.LogDebug("[CalculateTotalDamage] " + source + ": " + $"physical={dmg.m_damage}, blunt={dmg.m_blunt}, slash={dmg.m_slash}, " + $"pierce={dmg.m_pierce}, fire={dmg.m_fire}, frost={dmg.m_frost}, " + $"lightning={dmg.m_lightning}, poison={dmg.m_poison}, spirit={dmg.m_spirit}"); float num = dmg.m_damage + dmg.m_slash + dmg.m_pierce + dmg.m_blunt + dmg.m_fire + dmg.m_frost + dmg.m_lightning + dmg.m_poison + dmg.m_spirit; _logger.LogDebug(string.Format("[{0}] Total from {1}: {2}", "CalculateTotalDamage", source, num)); return num; } } public sealed class DifficultyMultiplierCalculator : IDifficultyMultiplierCalculator { private const float PlayerCountRadius = 200f; private const float DamagePerExtraPlayer = 0.04f; private const string EnemyDamageKey = "EnemyDamage"; private const string PlayerDamageKey = "PlayerDamage"; private readonly ILogger _logger; private readonly IPlayerWrapper _playerWrapper; private readonly IZoneSystemWrapper _zoneSystemWrapper; private readonly IMathfWrapper _mathfWrapper; public DifficultyMultiplierCalculator(ILogger logger, IPlayerWrapper playerWrapper, IZoneSystemWrapper zoneSystemWrapper, IMathfWrapper mathfWrapper) { _logger = logger ?? throw new ArgumentNullException("logger"); _playerWrapper = playerWrapper ?? throw new ArgumentNullException("playerWrapper"); _zoneSystemWrapper = zoneSystemWrapper ?? throw new ArgumentNullException("zoneSystemWrapper"); _mathfWrapper = mathfWrapper ?? throw new ArgumentNullException("mathfWrapper"); } public float GetDamageMultiplier(Vector3 position) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) float worldDifficultyMultiplier = GetWorldDifficultyMultiplier(); float playerCountMultiplier = GetPlayerCountMultiplier(position); float num = worldDifficultyMultiplier * playerCountMultiplier; _logger.LogDebug("[GetDamageMultiplier] " + $"worldDifficulty={worldDifficultyMultiplier:F2}, " + $"playerMultiplier={playerCountMultiplier:F2}, " + $"total={num:F2}"); return num; } public float GetWorldDifficultyMultiplier() { float incomingDamageFactor = GetIncomingDamageFactor(); _logger.LogDebug(string.Format("[{0}] scale={1:F2}", "GetWorldDifficultyMultiplier", incomingDamageFactor)); return incomingDamageFactor; } public float GetPlayerCountMultiplier(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) int nearbyPlayerCount = GetNearbyPlayerCount(position); return 1f + (float)_mathfWrapper.Max(0, nearbyPlayerCount - 1) * 0.04f; } public int GetNearbyPlayerCount(Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return _playerWrapper.GetPlayersInRangeXZ(position, 200f); } public float GetIncomingDamageFactor() { try { if (!_zoneSystemWrapper.IsInitialized) { _logger.LogDebug("[GetIncomingDamageFactor] ZoneSystem not initialized"); return 1f; } if (_zoneSystemWrapper.GetGlobalKey("EnemyDamage", out string value)) { return ParseDamageMultiplier("EnemyDamage", value); } if (_zoneSystemWrapper.TryGetGlobalKeyValue("EnemyDamage", out string value2)) { return ParseDamageMultiplier("EnemyDamage", value2); } _logger.LogDebug("[GetIncomingDamageFactor] EnemyDamage not set, using default 1.0x"); return 1f; } catch (Exception ex) { _logger.LogError("Exception: " + ex.Message); return 1f; } } public float GetEnemyHealthFactor() { try { if (!_zoneSystemWrapper.IsInitialized) { return 1f; } if (_zoneSystemWrapper.GetGlobalKey("PlayerDamage", out string value) && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { float num = ((result > 0f) ? (100f / result) : 1f); _logger.LogDebug(string.Format("[{0}] {1}={2}% -> enemy HP {3:F2}x", "GetEnemyHealthFactor", "PlayerDamage", value, num)); return num; } return 1f; } catch (Exception ex) { _logger.LogError("Exception: " + ex.Message); return 1f; } } public bool HasGlobalKey(string key) { if (_zoneSystemWrapper.IsInitialized) { return _zoneSystemWrapper.GetGlobalKey(key); } return false; } List<string> IDifficultyMultiplierCalculator.GetAllGlobalKeys() { return _zoneSystemWrapper.GetGlobalKeys(); } private float ParseDamageMultiplier(string keyName, string value) { if (string.IsNullOrEmpty(value)) { return 1f; } if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { float num = result / 100f; _logger.LogDebug(string.Format("[{0}] {1}={2}% -> {3:F2}x", "ParseDamageMultiplier", keyName, value, num)); if (!(num > 0f)) { return 1f; } return num; } _logger.LogWarning("Failed to parse " + keyName + "='" + value + "', using default"); return 1f; } } public sealed class ThreatCalculationService : IThreatCalculationService { private const float MeleeRangeThreshold = 10f; private readonly ILogger _logger; private readonly IDamageEstimator _blockEstimator; private readonly IDamageEstimator _parryEstimator; private readonly Lazy<ICreatureAttackInspector?> _attackInspector; private readonly IDifficultyMultiplierCalculator _difficultyCalculator; private readonly IVector3Wrapper _vector3Wrapper; private readonly IMathfWrapper _mathfWrapper; public ThreatCalculationService(ILogger logger, IDamageEstimator blockEstimator, IDamageEstimator parryEstimator, Lazy<ICreatureAttackInspector?> attackInspector, IDifficultyMultiplierCalculator difficultyCalculator, IVector3Wrapper vector3Wrapper, IMathfWrapper mathfWrapper) { _logger = logger ?? throw new ArgumentNullException("logger"); _blockEstimator = blockEstimator ?? throw new ArgumentNullException("blockEstimator"); _parryEstimator = parryEstimator ?? throw new ArgumentNullException("parryEstimator"); _attackInspector = attackInspector ?? throw new ArgumentNullException("attackInspector"); _difficultyCalculator = difficultyCalculator ?? throw new ArgumentNullException("difficultyCalculator"); _vector3Wrapper = vector3Wrapper ?? throw new ArgumentNullException("vector3Wrapper"); _mathfWrapper = mathfWrapper ?? throw new ArgumentNullException("mathfWrapper"); } public ThreatAssessment? CalculateThreat(Character enemy, Player player, bool detailedMode) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)enemy == (Object)null || (Object)(object)player == (Object)null) { return null; } Humanoid val = (Humanoid)(object)((enemy is Humanoid) ? enemy : null); if (val == null) { _logger.LogDebug("[CalculateThreat] Enemy " + enemy.m_name + " is not Humanoid"); return null; } float distance = _vector3Wrapper.Distance(((Component)enemy).transform.position, ((Component)player).transform.position); float baseDamage; float maxMeleeDamage; float maxRangedDamage; bool usedRangedAttack; if (detailedMode) { (baseDamage, maxMeleeDamage, maxRangedDamage, usedRangedAttack) = CalculateMaxAttackDetailed(val, distance); } else { float num = CalculateMaxAttackSimple(val); usedRangedAttack = false; maxRangedDamage = 0f; maxMeleeDamage = 0f; baseDamage = num; } float rawDamage = ApplyDifficultyMultipliers(enemy, baseDamage); DamageInfo damageInfo = CalculateDamageInfo(player, rawDamage); float num2 = CalculateDamageRatio(player, damageInfo.EffectiveDamageWithBlock); ThreatLevel threatLevel = DetermineThreatLevel(CalculateDamageRatio(player, damageInfo.EffectiveDamageWithBlock), CalculateDamageRatio(player, damageInfo.EffectiveDamageWithParry)); LogThreatCalculation(enemy, distance, baseDamage, rawDamage, damageInfo, num2, threatLevel); return new ThreatAssessment(threatLevel, damageInfo, num2, maxMeleeDamage, maxRangedDamage, usedRangedAttack); } public ThreatLevel DetermineThreatLevel(float blockRatio, float parryRatio) { if (parryRatio >= 1f) { return ThreatLevel.Danger; } if (blockRatio >= 1f && parryRatio < 1f) { return ThreatLevel.BlockLethal; } if (blockRatio >= 0.3f) { return ThreatLevel.Caution; } return ThreatLevel.Safe; } private float CalculateMaxAttackSimple(Humanoid humanoid) { if (_attackInspector.Value == null) { return 0f; } float maxAttackForCharacter = _attackInspector.Value.GetMaxAttackForCharacter((Character)(object)humanoid); _logger.LogDebug(string.Format("[{0}] {1}: maxAttack={2:F1}", "CalculateMaxAttackSimple", ((Character)humanoid).m_name, maxAttackForCharacter)); return maxAttackForCharacter; } private (float baseDamage, float maxMelee, float maxRanged, bool usedRanged) CalculateMaxAttackDetailed(Humanoid humanoid, float distance) { (float maxMelee, float maxRanged) weaponDamages = GetWeaponDamages(humanoid); float item = weaponDamages.maxMelee; float item2 = weaponDamages.maxRanged; (float damage, bool isRanged) currentAttackInfo = GetCurrentAttackInfo(humanoid); float item3 = currentAttackInfo.damage; bool item4 = currentAttackInfo.isRanged; bool flag = DetermineAttackType(distance, item, item2, item4); float num = SelectBaseDamage(item, item2, flag); _logger.LogDebug("[CalculateMaxAttackDetailed] " + ((Character)humanoid).m_name + ": " + $"melee={item:F1}, ranged={item2:F1}, " + $"currentAttack={item3:F1}, useRanged={flag}, " + $"dist={distance:F1}, baseDamage={num:F1}"); return (num, item, item2, flag); } private (float maxMelee, float maxRanged) GetWeaponDamages(Humanoid humanoid) { if ((Object)(object)humanoid == (Object)null) { return (0f, 0f); } ItemData item = HumanoidMethodRefs.GetRightItem?.Invoke(humanoid); ItemData item2 = HumanoidMethodRefs.GetLeftItem?.Invoke(humanoid); float itemDamage = GetItemDamage(item); float itemDamage2 = GetItemDamage(item2); float item3 = _mathfWrapper.Max(itemDamage, itemDamage2); float item4 = 0f; return (item3, item4); } private (float damage, bool isRanged) GetCurrentAttackInfo(Humanoid humanoid) { if ((Object)(object)humanoid == (Object)null) { return (0f, false); } FieldRef<Humanoid, Attack?>? currentAttackRef = HumanoidFieldRefs.CurrentAttackRef; Attack val = ((currentAttackRef != null) ? currentAttackRef.Invoke(humanoid) : null); if (val == null) { return (0f, false); } FieldRef<Attack, ItemData?>? weaponRef = AttackFieldRefs.WeaponRef; ItemData val2 = ((weaponRef != null) ? weaponRef.Invoke(val) : null); float num = ((val2 != null) ? GetItemDamage(val2) : 0f); FieldRef<Attack, float>? damageMultiplierRef = AttackFieldRefs.DamageMultiplierRef; float num2 = ((damageMultiplierRef != null) ? damageMultiplierRef.Invoke(val) : 1f); if (num2 > 0f) { num *= num2; } FieldRef<Attack, AttackType>? attackTypeRef = AttackFieldRefs.AttackTypeRef; int num3 = ((attackTypeRef != null) ? ((int)attackTypeRef.Invoke(val)) : 0); FieldRef<Attack, GameObject?>? projectilePrefabRef = AttackFieldRefs.ProjectilePrefabRef; GameObject val3 = ((projectilePrefabRef != null) ? projectilePrefabRef.Invoke(val) : null); bool item = num3 == 2 || (Object)(object)val3 != (Object)null; return (num, item); } private float GetItemDamage(ItemData? item) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (item == null) { return 0f; } DamageTypes damages = item.m_shared.m_damages; return damages.m_damage + damages.m_blunt + damages.m_slash + damages.m_pierce + damages.m_chop + damages.m_pickaxe + damages.m_fire + damages.m_frost + damages.m_lightning + damages.m_poison + damages.m_spirit; } private bool DetermineAttackType(float distance, float maxMelee, float maxRanged, bool currentIsRanged) { if (distance <= 10f && maxMelee > 0f) { return false; } if (maxRanged > 0f) { return true; } return currentIsRanged; } private float SelectBaseDamage(float maxMelee, float maxRanged, bool useRanged) { if (useRanged && maxRanged > 0f) { return maxRanged; } if (!useRanged && maxMelee > 0f) { return maxMelee; } return _mathfWrapper.Max(maxMelee, maxRanged); } private float ApplyDifficultyMultipliers(Character enemy, float baseDamage) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)enemy).transform.position; float damageMultiplier = _difficultyCalculator.GetDamageMultiplier(position); int level = enemy.GetLevel(); float num = 1f + 0.4f * (float)(level - 1); return baseDamage * damageMultiplier * num; } private DamageInfo CalculateDamageInfo(Player player, float rawDamage) { float effectiveWithBlock = _blockEstimator.EstimateEffectiveDamage(player, rawDamage); float effectiveWithParry = _parryEstimator.EstimateEffectiveDamage(player, rawDamage); return new DamageInfo(rawDamage, effectiveWithBlock, effectiveWithParry); } private float CalculateDamageRatio(Player player, float effectiveDamage) { float health = ((Character)player).GetHealth(); if (!(health > 0f)) { return 0f; } return effectiveDamage / health; } private void LogThreatCalculation(Character enemy, float distance, float baseDamage, float rawDamage, DamageInfo damageInfo, float ratio, ThreatLevel threatLevel) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)enemy).transform.position; float worldDifficultyMultiplier = _difficultyCalculator.GetWorldDifficultyMultiplier(); int nearbyPlayerCount = _difficultyCalculator.GetNearbyPlayerCount(position); float playerCountMultiplier = _difficultyCalculator.GetPlayerCountMultiplier(position); float damageMultiplier = _difficultyCalculator.GetDamageMultiplier(position); _logger.LogDebug("[LogThreatCalculation] name=" + enemy.m_name + ", " + $"lvl={enemy.GetLevel()}, " + $"dist={distance:F1}m, " + $"base={baseDamage:F1}, " + $"worldDiff={worldDifficultyMultiplier:F2}x, " + $"players={nearbyPlayerCount} ({playerCountMultiplier:F2}x), " + $"totalMult={damageMultiplier:F2}x, " + $"raw={rawDamage:F1}," + $"effBlock={damageInfo.EffectiveDamageWithBlock:F1}, " + $"effParry={damageInfo.EffectiveDamageWithParry:F1}, " + $"ratio={ratio:F2}, " + $"threat={threatLevel}"); } } public sealed class ThreatResponseHintService : IThreatResponseHintService { private const float HighRiskRatio = 0.7f; public ThreatResponseHint GetHint(ThreatAssessment assessment) { if (assessment == null) { throw new ArgumentNullException("assessment"); } return assessment.Level switch { ThreatLevel.Safe => ThreatResponseHint.Block, ThreatLevel.Caution => ThreatResponseHint.Block, ThreatLevel.BlockLethal => ThreatResponseHint.Parry, ThreatLevel.Danger => ThreatResponseHint.Dodge, _ => ThreatResponseHint.None, }; } } } namespace Valheim.Foresight.Services.Combat.Wrappers { public sealed class MathfWrapper : IMathfWrapper { public int Max(int a, int b) { return Mathf.Max(a, b); } public float Max(float a, float b) { return Mathf.Max(a, b); } } public sealed class PlayerWrapper : IPlayerWrapper { public int GetPlayersInRangeXZ(Vector3 position, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Player.GetPlayersInRangeXZ(position, radius); } } public sealed class Vector3Wrapper : IVector3Wrapper { public float Distance(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(a, b); } } public sealed class ZNetSceneWrapper : IZNetSceneWrapper { private readonly ZNetScene _zNetScene; public ZNetSceneWrapper(ZNetScene zNetScene) { _zNetScene = zNetScene ?? throw new ArgumentNullException("zNetScene"); } public GameObject? GetPrefab(string name) { return _zNetScene.GetPrefab(name); } } public sealed class ZoneSystemWrapper : IZoneSystemWrapper { public bool IsInitialized => (Object)(object)ZoneSystem.instance != (Object)null; public bool GetGlobalKey(string key, out string value) { value = string.Empty; if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } return ZoneSystem.instance.GetGlobalKey(key, ref value); } public bool GetGlobalKey(string key) { if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } return ZoneSystem.instance.GetGlobalKey(key); } public bool TryGetGlobalKeyValue(string key, out string value) { value = string.Empty; if ((Object)(object)ZoneSystem.instance == (Object)null || ZoneSystem.instance.m_globalKeysValues == null) { return false; } return ZoneSystem.instance.m_globalKeysValues.TryGetValue(key, out value); } public List<string> GetGlobalKeys() { ZoneSystem instance = ZoneSystem.instance; return ((instance != null) ? instance.GetGlobalKeys() : null) ?? new List<string>(); } } } namespace Valheim.Foresight.Services.Combat.Interfaces { public interface ICreatureAttackInspector { float GetMaxAttackByPrefabName(string prefabName); float GetMaxAttackForCharacter(Character character); } public interface IDifficultyMultiplierCalculator { float GetDamageMultiplier(Vector3 position); float GetWorldDifficultyMultiplier(); float GetPlayerCountMultiplier(Vector3 position); int GetNearbyPlayerCount(Vector3 position); float GetIncomingDamageFactor(); float GetEnemyHealthFactor(); List<string> GetAllGlobalKeys(); } public interface IMathfWrapper { int Max(int a, int b); float Max(float a, float b); } public interface IPlayerWrapper { int GetPlayersInRangeXZ(Vector3 position, float radius); } public interface IThreatCalculationService { ThreatAssessment? CalculateThreat(Character enemy, Player player, bool detailedMode); ThreatLevel DetermineThreatLevel(float blockRatio, float parryRatio); } public interface IThreatResponseHintService { ThreatResponseHint GetHint(ThreatAssessment assessment); } public interface IVector3Wrapper { float Distance(Vector3 a, Vector3 b); } public interface IZNetSceneWrapper { GameObject? GetPrefab(string name); } public interface IZoneSystemWrapper { bool IsInitialized { get; } bool GetGlobalKey(string key, out string value); bool GetGlobalKey(string key); bool TryGetGlobalKeyValue(string key, out string value); List<string> GetGlobalKeys(); } } namespace Valheim.Foresight.Services.Castbar { public sealed class ActiveAttackTracker : IActiveAttackTracker { private readonly Dictionary<Character, ActiveAttackInfo> _activeAttacks = new Dictionary<Character, ActiveAttackInfo>(); public void RegisterAttack(Character attacker, Attack attack, float duration, float startTime, float? predictedHitTime, string? animationName, bool hideParryIndicator) { _activeAttacks[attacker] = new ActiveAttackInfo(attacker, attack, duration, startTime, predictedHitTime, animationName, hideParryIndicator); } public ActiveAttackInfo? GetActiveAttack(Character attacker) { if (_activeAttacks.TryGetValue(attacker, out ActiveAttackInfo value)) { if (value.IsExpired) { _activeAttacks.Remove(attacker); return null; } return value; } return null; } public void CleanupExpired() { List<Character> list = new List<Character>(); foreach (KeyValuePair<Character, ActiveAttackInfo> activeAttack in _activeAttacks) { if (activeAttack.Value.IsExpired) { list.Add(activeAttack.Key); } } foreach (Character item in list) { _activeAttacks.Remove(item); } } } public sealed class AttackTimingService : IAttackTimingService, IDisposable, IAttackTimingDataProvider { private const string DataFileName = "attack_timings.yml"; private const string PrelearnedDataFileName = "attack_timings_prelearned.yml"; private const float AutoSaveIntervalSeconds = 30f; private const int MinSamplesForPrediction = 2; private const string UnknownKeyName = "unknown"; private const string TimingsDbDirectoryName = "foresight.Database"; pr