Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DynamicReticle v0.5.6
DynamicReticle.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("DynamicReticle")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("DynamicReticle")] [assembly: AssemblyTitle("DynamicReticle")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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; } } } namespace DynamicReticle { [BepInPlugin("kajetan.valheim.dynamicreticle", "Wire's Dynamic Reticles", "0.5.6")] public sealed class DynamicReticlePlugin : BaseUnityPlugin { private enum AttackGroup { None, Spear, Magic, Other } public enum EasingMode { Linear, SineInOut, ExpoInOut } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] private static class HudUpdateCrosshairPatch { private static void Postfix(Hud __instance, Player player, float bowDrawPercentage) { if ((Object)(object)Instance != (Object)null) { Instance.UpdateHud(__instance, player, bowDrawPercentage); } } } [HarmonyPatch(typeof(Hud), "UpdateStealth")] private static class HudUpdateStealthPatch { private static void Postfix(Hud __instance) { DisableStealthHud(__instance); } } [HarmonyPatch(typeof(Attack), "Start")] private static class AttackStartPatch { private static void Postfix(Attack __instance, Humanoid character, ItemData weapon, ref bool __result) { if ((Object)(object)Instance != (Object)null) { Instance.OnAttackStarted(__instance, character, weapon, __result); } } } [HarmonyPatch(typeof(Attack), "OnAttackTrigger")] private static class AttackTriggerPatch { private static void Postfix(Attack __instance) { if ((Object)(object)Instance != (Object)null) { Instance.OnAttackTriggered(__instance); } } } [HarmonyPatch(typeof(Attack), "Stop")] private static class AttackStopPatch { private static void Postfix(Attack __instance) { if ((Object)(object)Instance != (Object)null) { Instance.OnAttackStopped(__instance); } } } public const string PluginGuid = "kajetan.valheim.dynamicreticle"; public const string PluginName = "Wire's Dynamic Reticles"; public const string PluginVersion = "0.5.6"; private const float VanillaStartScale = 1f; private const float VanillaEndScale = 0.15f; private const float VanillaCrosshairIdleAlpha = 0.5f; internal static DynamicReticlePlugin Instance; private Harmony _harmony; private Image _customRing; private Hud _customRingHud; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _disableStealth; private ConfigEntry<bool> _showHeldCrosshair; private ConfigEntry<bool> _showHoverCrosshair; private ConfigEntry<float> _crosshairFade; private ConfigEntry<bool> _reticleEnabled; private ConfigEntry<EasingMode> _easing; private float _crosshairVisibility; private bool _bowReleaseWaitingForZero; private Attack _activeAttack; private ItemData _activeWeapon; private AttackGroup _activeGroup; private bool _activeReleased; private readonly Dictionary<string, float> _learnedReleaseTimes = new Dictionary<string, float>(StringComparer.Ordinal); private static readonly FieldInfo ActionQueueField = AccessTools.Field(typeof(Player), "m_actionQueue"); private static readonly FieldInfo AttackCharacterField = AccessTools.Field(typeof(Attack), "m_character"); private static readonly FieldInfo AttackTimeField = AccessTools.Field(typeof(Attack), "m_time"); private void Awake() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown Instance = this; BindConfig(); _harmony = new Harmony("kajetan.valheim.dynamicreticle"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Wire's Dynamic Reticles 0.5.6 loaded."); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } Instance = null; } private void BindConfig() { _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch."); _disableStealth = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DisableStealthIndicator", true, "Hide Valheim's crouch/stealth HUD indicator."); _showHeldCrosshair = ((BaseUnityPlugin)this).Config.Bind<bool>("Crosshair", "ShowForWeaponsAndTools", true, "Show the normal crosshair while holding any weapon or tool."); _showHoverCrosshair = ((BaseUnityPlugin)this).Config.Bind<bool>("Crosshair", "ShowOnHover", true, "Show the normal crosshair when the aimed object has Valheim hover text/name or is interactable."); _crosshairFade = ((BaseUnityPlugin)this).Config.Bind<float>("Crosshair", "FadeSeconds", 0.1f, "Normal-crosshair fade duration in and out."); _reticleEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Reticle", "Enabled", true, "Enable the animated yellow projectile reticle."); _easing = ((BaseUnityPlugin)this).Config.Bind<EasingMode>("Reticle", "Easing", EasingMode.SineInOut, "Reticle scale/color easing. SineInOut is the default; ExpoInOut is punchier."); } internal void UpdateHud(Hud hud, Player player, float bowDrawPercentage) { //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Invalid comparison between Unknown and I4 if ((Object)(object)hud == (Object)null || (Object)(object)player == (Object)null) { return; } if (!_enabled.Value) { if ((Object)(object)_customRing != (Object)null) { ((Component)_customRing).gameObject.SetActive(false); } return; } EnsureCustomRing(hud); if ((Object)(object)hud.m_crosshairBow != (Object)null) { ((Component)hud.m_crosshairBow).gameObject.SetActive(false); } ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon(); ItemData rightItem = ((Humanoid)player).RightItem; UpdateNormalCrosshair(hud, player, rightItem ?? currentWeapon, currentWeapon); if (_reticleEnabled.Value && bowDrawPercentage > 0f) { if (_bowReleaseWaitingForZero) { HideRing(hud); } else { RenderProgress(hud, bowDrawPercentage, useConfiguredEasing: true); } return; } if (bowDrawPercentage <= 0f && _bowReleaseWaitingForZero) { _bowReleaseWaitingForZero = false; } if (!_reticleEnabled.Value) { HideRing(hud); } else if (currentWeapon == null || currentWeapon.m_shared == null) { HideRing(hud); } else if ((int)currentWeapon.m_shared.m_skillType == 14) { RenderCrossbow(hud, player, currentWeapon); } else if (IsDundr(currentWeapon)) { RenderDundr(hud, player, currentWeapon); } else if (_activeAttack != null && _activeWeapon == currentWeapon) { RenderActiveAttack(hud); } else { HideRing(hud); } } private void UpdateNormalCrosshair(Hud hud, Player player, ItemData heldItem, ItemData currentWeapon) { //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) //IL_010a: Unknown result type (might be due to invalid IL or missing references) bool flag = false; bool flag2 = false; GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject != (Object)null) { try { if (_showHoverCrosshair.Value) { flag = hoverObject.GetComponentInParent<Interactable>() != null; Hoverable componentInParent = hoverObject.GetComponentInParent<Hoverable>(); if (componentInParent != null) { string hoverText = componentInParent.GetHoverText(); string hoverName = componentInParent.GetHoverName(); flag2 = !string.IsNullOrEmpty(hoverText) || !string.IsNullOrEmpty(hoverName); } } } catch { flag = false; flag2 = false; } } bool flag3 = flag || flag2 || (_showHeldCrosshair.Value && IsHeldWeaponOrTool(heldItem, currentWeapon)); float num = Mathf.Max(0.001f, _crosshairFade.Value); _crosshairVisibility = Mathf.MoveTowards(_crosshairVisibility, flag3 ? 1f : 0f, Time.unscaledDeltaTime / num); Image crosshair = hud.m_crosshair; if (!((Object)(object)crosshair == (Object)null)) { Color color = ((Graphic)crosshair).color; float num2 = Mathf.Max(color.a, 0.5f); color.a = num2 * _crosshairVisibility; ((Graphic)crosshair).color = color; } } private static bool IsHeldWeaponOrTool(ItemData heldItem, ItemData currentWeapon) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0029: Invalid comparison between Unknown and I4 //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 if (currentWeapon != null && currentWeapon.m_shared != null) { return true; } if (heldItem == null || heldItem.m_shared == null) { return false; } ItemType itemType = heldItem.m_shared.m_itemType; if ((int)itemType != 19) { return (int)itemType == 15; } return true; } private void RenderCrossbow(Hud hud, Player player, ItemData weapon) { if (TryGetReloadProgress(player, weapon, out var progress)) { RenderProgress(hud, progress); } else if (((Humanoid)player).IsWeaponLoaded()) { RenderProgress(hud, 1f); } else { HideRing(hud); } } private void RenderDundr(Hud hud, Player player, ItemData weapon) { if (TryGetReloadProgress(player, weapon, out var progress)) { RenderProgress(hud, progress); } else if (((Humanoid)player).IsWeaponLoaded()) { RenderProgress(hud, 1f); } else if (_activeAttack != null && _activeWeapon == weapon && !_activeReleased) { RenderActiveAttack(hud); } else { HideRing(hud); } } private bool TryGetReloadProgress(Player player, ItemData weapon, out float progress) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 progress = 0f; if (ActionQueueField == null) { return false; } if (!(ActionQueueField.GetValue(player) is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { MinorActionData val = (MinorActionData)((item is MinorActionData) ? item : null); if (val != null && (int)val.m_type == 2 && (val.m_item == null || val.m_item == weapon)) { progress = ((val.m_duration <= 0f) ? 1f : Mathf.Clamp01(val.m_time / val.m_duration)); return true; } } return false; } private void RenderActiveAttack(Hud hud) { if (_activeAttack == null) { HideRing(hud); } else if (_activeReleased) { if (IsSustainedBurstAttack(_activeAttack)) { RenderProgress(hud, 1f); } else { HideRing(hud); } } else { float attackElapsed = GetAttackElapsed(_activeAttack); float expectedReleaseSeconds = GetExpectedReleaseSeconds(_activeAttack, _activeWeapon, _activeGroup); float rawProgress = ((expectedReleaseSeconds <= 0.001f) ? 0f : Mathf.Clamp01(attackElapsed / expectedReleaseSeconds)); RenderProgress(hud, rawProgress); } } private void RenderProgress(Hud hud, float rawProgress) { RenderProgress(hud, rawProgress, useConfiguredEasing: true); } private void RenderProgress(Hud hud, float rawProgress, bool useConfiguredEasing) { //IL_0078: 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_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) EnsureCustomRing(hud); if (!((Object)(object)_customRing == (Object)null)) { rawProgress = Mathf.Clamp01(rawProgress); if (rawProgress <= 0f) { ((Component)_customRing).gameObject.SetActive(false); return; } float num = (useConfiguredEasing ? Ease(rawProgress) : rawProgress); float num2 = Mathf.Lerp(GetStartScale(), GetEndScale(), num); ((Component)_customRing).gameObject.SetActive(true); ((Component)_customRing).transform.localScale = new Vector3(num2, num2, num2); ((Graphic)_customRing).color = Color.Lerp(new Color(1f, 1f, 1f, 0f), Color.yellow, num); } } internal void OnAttackStarted(Attack attack, Humanoid character, ItemData weapon, bool started) { if (started && _enabled.Value && attack != null && !((Object)(object)character != (Object)(object)Player.m_localPlayer) && weapon != null && weapon.m_shared != null) { AttackGroup attackGroup = ClassifyAttack(weapon, attack); if (attackGroup != AttackGroup.None) { _activeAttack = attack; _activeWeapon = weapon; _activeGroup = attackGroup; _activeReleased = false; } } } internal void OnAttackTriggered(Attack attack) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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_0047: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 if (!_enabled.Value || attack == null) { return; } Humanoid attackCharacter = GetAttackCharacter(attack); if ((Object)(object)attackCharacter != (Object)(object)Player.m_localPlayer) { return; } ItemData currentWeapon = attackCharacter.GetCurrentWeapon(); if (currentWeapon == null || currentWeapon.m_shared == null) { return; } SkillType skillType = currentWeapon.m_shared.m_skillType; if ((int)skillType == 8) { HideRing(Hud.instance); _bowReleaseWaitingForZero = true; } else if ((int)skillType == 14) { HideRing(Hud.instance); } else if (attack == _activeAttack) { float attackElapsed = GetAttackElapsed(attack); if (attackElapsed > 0.03f && attackElapsed < 10f) { _learnedReleaseTimes[GetAttackKey(attack, _activeWeapon)] = attackElapsed; } _activeReleased = true; if (!IsSustainedBurstAttack(attack)) { HideRing(Hud.instance); } } } internal void OnAttackStopped(Attack attack) { if (attack != null && attack == _activeAttack) { if (_activeReleased && IsSustainedBurstAttack(attack)) { HideRing(Hud.instance); } _activeAttack = null; _activeWeapon = null; _activeGroup = AttackGroup.None; _activeReleased = false; } } private AttackGroup ClassifyAttack(ItemData weapon, Attack attack) { //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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (weapon == null || weapon.m_shared == null || !IsProjectileAttack(attack)) { return AttackGroup.None; } SkillType skillType = weapon.m_shared.m_skillType; if ((int)skillType == 8 || (int)skillType == 14) { return AttackGroup.None; } if ((int)skillType == 5) { return AttackGroup.Spear; } if (IsMagicSkill(skillType)) { if (IsUtilityMagicStaff(weapon)) { return AttackGroup.None; } return AttackGroup.Magic; } return AttackGroup.Other; } private bool IsRangedWeaponForCrosshair(ItemData weapon) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (weapon == null || weapon.m_shared == null) { return false; } SkillType skillType = weapon.m_shared.m_skillType; if ((int)skillType == 8 || (int)skillType == 14 || (int)skillType == 5) { return true; } if (IsMagicSkill(skillType) && IsUtilityMagicStaff(weapon)) { return false; } if (!IsProjectileAttack(weapon.m_shared.m_attack)) { return IsProjectileAttack(weapon.m_shared.m_secondaryAttack); } return true; } private float GetExpectedReleaseSeconds(Attack attack, ItemData weapon, AttackGroup group) { string attackKey = GetAttackKey(attack, weapon); if (_learnedReleaseTimes.TryGetValue(attackKey, out var value) && value > 0.03f) { return value; } return group switch { AttackGroup.Spear => 0.55f, AttackGroup.Magic => 0.42f, _ => 0.4f, }; } private float GetScaleForRawProgress(float rawProgress) { float num = Ease(Mathf.Clamp01(rawProgress)); return Mathf.Lerp(GetStartScale(), GetEndScale(), num); } private float GetStartScale() { return 1f; } private float GetEndScale() { return 0.15f; } private float Ease(float t) { t = Mathf.Clamp01(t); switch (_easing.Value) { case EasingMode.Linear: return t; case EasingMode.ExpoInOut: if (t <= 0f) { return 0f; } if (t >= 1f) { return 1f; } if (!(t < 0.5f)) { return (2f - Mathf.Pow(2f, -20f * t + 10f)) / 2f; } return Mathf.Pow(2f, 20f * t - 10f) / 2f; default: return (0f - (Mathf.Cos((float)Math.PI * t) - 1f)) / 2f; } } private static bool IsSustainedBurstAttack(Attack attack) { if (attack != null) { return attack.m_projectileBursts > 1; } return false; } private static string GetAttackKey(Attack attack, ItemData weapon) { string text = ((weapon != null && weapon.m_shared != null) ? (weapon.m_shared.m_name ?? string.Empty) : string.Empty); string text2 = ((attack != null) ? (attack.m_attackAnimation ?? string.Empty) : string.Empty); return text + "|" + text2 + "|" + ((attack != null) ? ((object)Unsafe.As<AttackType, AttackType>(ref attack.m_attackType)/*cast due to .constrained prefix*/).ToString() : string.Empty); } private static float GetAttackElapsed(Attack attack) { if (attack == null || AttackTimeField == null) { return 0f; } object value = AttackTimeField.GetValue(attack); if (!(value is float)) { return 0f; } return (float)value; } private static Humanoid GetAttackCharacter(Attack attack) { if (attack == null || AttackCharacterField == null) { return null; } object? value = AttackCharacterField.GetValue(attack); return (Humanoid)((value is Humanoid) ? value : null); } private static bool IsProjectileAttack(Attack attack) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if (attack != null) { if ((int)attack.m_attackType != 2) { return (int)attack.m_attackType == 5; } return true; } return false; } private static bool IsMagicSkill(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)skill != 9) { return (int)skill == 10; } return true; } private static bool IsDundr(ItemData weapon) { if (weapon != null && weapon.m_shared != null) { return string.Equals(weapon.m_shared.m_name, "$item_staff_lightning", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsUtilityMagicStaff(ItemData weapon) { if (weapon == null || weapon.m_shared == null) { return false; } switch ((weapon.m_shared.m_name ?? string.Empty).ToLowerInvariant()) { case "$item_staffshield": case "$item_staffskeleton": case "$item_staffredtroll": case "$item_staff_frostorbs": case "$item_staff_spiritcaller": return true; default: return false; } } internal static void DisableStealthHud(Hud hud) { if (!((Object)(object)Instance == (Object)null) && Instance._enabled.Value && Instance._disableStealth.Value && !((Object)(object)hud == (Object)null)) { if ((Object)(object)hud.m_targetedAlert != (Object)null) { hud.m_targetedAlert.SetActive(false); } if ((Object)(object)hud.m_targeted != (Object)null) { hud.m_targeted.SetActive(false); } if ((Object)(object)hud.m_hidden != (Object)null) { hud.m_hidden.SetActive(false); } if ((Object)(object)hud.m_stealthBar != (Object)null) { ((Component)hud.m_stealthBar).gameObject.SetActive(false); } } } private void EnsureCustomRing(Hud hud) { if (!((Object)(object)hud == (Object)null) && !((Object)(object)hud.m_crosshairBow == (Object)null) && (!((Object)(object)_customRing != (Object)null) || !((Object)(object)_customRingHud == (Object)(object)hud))) { if ((Object)(object)_customRing != (Object)null) { Object.Destroy((Object)(object)((Component)_customRing).gameObject); } _customRingHud = hud; _customRing = Object.Instantiate<Image>(hud.m_crosshairBow, ((Component)hud.m_crosshairBow).transform.parent, false); ((Object)_customRing).name = "DynamicReticleRing"; ((Graphic)_customRing).raycastTarget = false; ((Component)_customRing).transform.SetSiblingIndex(((Component)hud.m_crosshairBow).transform.GetSiblingIndex() + 1); ((Component)_customRing).gameObject.SetActive(false); } } private void HideRing(Hud hud) { if ((Object)(object)_customRing != (Object)null) { ((Component)_customRing).gameObject.SetActive(false); } } } }