Decompiled source of Valheim Foresight v2.0.1
BepInEx/plugins/Valheim.Foresight.dll
Decompiled 2 weeks 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