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 Feint v0.3.0
Feint.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Feint")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: AssemblyInformationalVersion("0.3.0+40c4fb60e823969d89ab175293814249f7d5948c")] [assembly: AssemblyProduct("Feint")] [assembly: AssemblyTitle("Feint")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.0.0")] [module: UnverifiableCode] [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 Feint { internal static class Cancel { [HarmonyPatch(typeof(Humanoid), "StartAttack")] private static class StartAttackPatch { private static void Postfix(Humanoid __instance, bool __result, float __state) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } if (__result) { s_attackStartTime = Time.time; Attack currentAttack = __instance.m_currentAttack; if (Plugin.DebugLog.Value && currentAttack != null) { Attack previousAttack = __instance.m_previousAttack; Debug($"swing {currentAttack.m_attackAnimation} level {currentAttack.m_currentAttackCainLevel}/{currentAttack.m_attackChainLevels}" + ((previousAttack == null) ? ", no previous" : $", previous {previousAttack.m_attackAnimation} level {previousAttack.m_currentAttackCainLevel} next {previousAttack.m_nextAttackChainLevel}") + $", {((__state >= 0f) ? __state : __instance.m_timeSinceLastAttack):0.##}s since the last swing"); } if (__state > 0.2f) { Debug($"combo kept {__state:0.##}s after the last swing"); s_keptSwing = __instance.m_currentAttack; TurnKeptCombo((Player)__instance); } } else if (__state >= 0f) { __instance.m_timeSinceLastAttack = __state; } } [HarmonyPriority(800)] private static bool Prefix(Humanoid __instance, out float __state) { //IL_0097: 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) __state = -1f; if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if ((Object)(object)s_pendingPlayer != (Object)null) { return false; } Attack currentAttack = __instance.m_currentAttack; if (Plugin.KeepCombo.Value && currentAttack != null && currentAttack == s_keptSwing && !currentAttack.m_attackDone && !currentAttack.m_wasInAttack && currentAttack.m_time < 0.5f) { return false; } Animator animator = ((Character)__instance).m_animator; if (s_restoreState.Length != animator.layerCount) { s_restoreState = new int[animator.layerCount]; } for (int i = 0; i < animator.layerCount; i++) { if (!animator.IsInTransition(i)) { AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(i); if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).tagHash != Humanoid.s_animatorTagAttack) { s_restoreState[i] = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash; } } } Attack val = __instance.m_currentAttack ?? __instance.m_previousAttack; if (val != null && KeepsCombo(HitDone(val)) && val.GetWeapon() == __instance.GetCurrentWeapon() && __instance.m_timeSinceLastAttack <= Plugin.ComboKeepSeconds.Value) { __state = __instance.m_timeSinceLastAttack; __instance.m_timeSinceLastAttack = 0f; } return true; } } [HarmonyPatch(typeof(Attack), "OnAttackTrigger")] private static class OnAttackTriggerPatch { private static void Prefix(Attack __instance) { if (!((Object)(object)__instance.m_character == (Object)null) && !((Object)(object)__instance.m_character != (Object)(object)Player.m_localPlayer) && !((Character)__instance.m_character).IsStaggering() && Attack.HaveAmmo(__instance.m_character, __instance.m_weapon)) { Track(__instance); s_triggered = true; s_hitQueueCheck = true; if (__instance == s_pendingAttack && !s_pendingAfterHit) { s_pendingHit = true; s_pendingPulledBack = ((s_pendingDelay > 0f) ? Mathf.Clamp01((Time.time - s_pendingSince) / s_pendingDelay) : 1f); float value = Plugin.CancelledHitDamage.Value; float num = (Plugin.ScaleCancelledHitDamage.Value ? Mathf.Lerp(1f, value, s_pendingPulledBack) : value); __instance.m_damageMultiplier = s_pendingBaseDamage * num; Debug($"cancelled swing hit {s_pendingPulledBack:P0} into the cancel delay: damage x{num:0.##}"); } } } } [HarmonyPatch(typeof(Player), "PlayerAttackInput")] private static class AttackInputPatch { private static void Prefix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && Plugin.DodgeAfterHit.Value && Plugin.CancelWithDodge.Value && (((Character)__instance).m_attack || ((Character)__instance).m_secondaryAttack) && __instance.m_queuedDodgeTimer > 0f && ((Character)__instance).InAttack() && !HitDone(((Humanoid)__instance).m_currentAttack)) { __instance.m_queuedDodgeTimer = 0f; Debug("queued dodge replaced by an attack press"); } } } [HarmonyPatch(typeof(Player), "Dodge")] private static class DodgePatch { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && __instance.m_queuedDodgeTimer > 0f) { TryCancel(__instance, dodge: true); } } } private static Attack s_tracked; private static bool s_triggered; private static int[] s_restoreState = new int[0]; private const float CrossFadeSeconds = 0.15f; private const float DelayOfElapsed = 0.5f; private static float s_attackStartTime; private static Player s_pendingPlayer; private static Attack s_pendingAttack; private static float s_pendingSince; private static float s_pendingDelay; private static float s_pendingBaseDamage; private static bool s_pendingDodge; private static Vector3 s_pendingDodgeDir; private static float s_pendingAt; private static float s_pendingPenalty; private static float s_pendingProgress; private static bool s_pendingAfterHit; private static bool s_pendingHit; private static float s_pendingPulledBack; private const float PeakProgress = 2f / 3f; private const float PenaltyAtHit = 0.5f; private const float QueuedDodgeSeconds = 0.5f; private static bool s_hitQueueCheck; private static Player s_checkPlayer; private static int s_checkFrame; private static float s_checkTime; private const int CheckFrames = 2; private const float CheckSeconds = 0.05f; private const float KeptComboMaxTurn = 90f; private static Attack s_keptSwing; private const float SwingStartGraceSeconds = 0.5f; internal static bool TryCancel(Player player, bool dodge, bool quiet = false) { //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) if (!(dodge ? Plugin.CancelWithDodge.Value : Plugin.CancelWithBlock.Value)) { return false; } Attack currentAttack = ((Humanoid)player).m_currentAttack; if (currentAttack == null || currentAttack.IsDone() || !((Character)player).InAttack() || (Object)(object)s_pendingPlayer != (Object)null) { return false; } Track(currentAttack); bool flag = s_triggered; if (flag && (!dodge || !Plugin.DodgeAfterHit.Value)) { Debug("refused: the swing has already hit"); return false; } if (!flag && IsExcluded(currentAttack.m_attackAnimation)) { Debug("refused: " + currentAttack.m_attackAnimation + " is in NonCancellableAttacks"); return false; } if (!flag && !((Character)player).IsOnGround()) { Debug("refused: in the air"); return false; } float num = Time.time - s_attackStartTime; float num2 = (flag ? 0f : (0.5f * num)); float num3 = ((flag && !Plugin.DodgeAfterHitPenalty.Value) ? 0f : (currentAttack.GetAttackStamina() * Plugin.StaminaPenalty.Value)); float num4 = num3 + (dodge ? player.GetDodgeStaminaUse() : 0f); if (num4 > 0f && !((Character)player).HaveStamina(num4)) { if (!quiet) { Hud instance = Hud.instance; if (instance != null) { instance.StaminaBarEmptyFlash(); } } Debug($"refused: not enough stamina ({num4:0.#} needed)"); return false; } player.m_queuedDodgeTimer = 0f; float num5 = (flag ? HitProgress.Recovery(((Character)player).m_animator) : HitProgress.Get(((Character)player).m_animator)); Debug("cancelled " + currentAttack.m_attackAnimation + " with " + (dodge ? "dodge" : "block") + ", " + $"{num:0.##}s into the swing " + string.Format("({0:P0} {1}), delay {2:0.##}s", num5, flag ? "through the recovery" : "to the hit", num2)); s_pendingPlayer = player; s_pendingAttack = currentAttack; s_pendingSince = Time.time; s_pendingDelay = num2; s_pendingBaseDamage = currentAttack.m_damageMultiplier; s_pendingDodge = dodge; s_pendingDodgeDir = player.m_queuedDodgeDir; s_pendingPenalty = num3; s_pendingProgress = num5; s_pendingAfterHit = flag; s_pendingHit = false; s_pendingAt = Time.time + num2; return true; } internal static void Update() { UpdateHitQueue(); UpdatePending(); UpdateCheck(); } private static void UpdateHitQueue() { if (s_hitQueueCheck) { s_hitQueueCheck = false; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !(localPlayer.m_queuedDodgeTimer <= 0f) && Plugin.DodgeAfterHit.Value && ((Humanoid)localPlayer).m_currentAttack == s_tracked) { Debug("dodge queued before the hit"); TryCancel(localPlayer, dodge: true, quiet: true); } } } private static void UpdatePending() { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_pendingPlayer == (Object)null) { return; } Player val = s_pendingPlayer; bool flag = (Object)(object)val == (Object)(object)Player.m_localPlayer && !((Character)val).InAttack(); if (!flag && (Time.time < s_pendingAt || (s_pendingAfterHit && !((Character)val).IsOnGround()))) { return; } Attack val2 = s_pendingAttack; s_pendingPlayer = null; s_pendingAttack = null; if ((Object)(object)val != (Object)(object)Player.m_localPlayer || ((Character)val).IsDead()) { return; } val2.Stop(); if (((Humanoid)val).m_currentAttack == val2) { ((Humanoid)val).m_currentAttack = null; } ((Humanoid)val).m_previousAttack = (KeepsCombo(s_pendingAfterHit || s_pendingHit) ? val2 : null); ResetAttackTriggers(((Character)val).m_animator, val2); val.m_queuedAttackTimer = 0f; val.m_queuedSecondAttackTimer = 0f; if (flag) { Debug("swing ended before the cancel delay: no penalty"); } else { float num = s_pendingPenalty * (Plugin.ScaleStaminaPenalty.Value ? PenaltyScale() : 1f); if (num > 0f) { ((Character)val).UseStamina(num); } Debug($"cancel delay over: penalty {num:0.#} of {s_pendingPenalty:0.#}"); } if (s_pendingDodge && CanDodge(val)) { StartDodge(val, s_pendingDodgeDir); if (!flag) { s_checkPlayer = val; s_checkFrame = Time.frameCount + 2; s_checkTime = Time.time + 0.05f; } } else if (s_pendingDodge && flag && !((Character)val).IsOnGround()) { val.m_queuedDodgeTimer = 0.5f; val.m_queuedDodgeDir = s_pendingDodgeDir; } else if (!flag) { CrossFadeOutOfAttack(((Character)val).m_animator); } } private static float PenaltyScale() { if (s_pendingAfterHit) { if (!(s_pendingProgress < 0f)) { return Mathf.Lerp(0.5f, 0f, s_pendingProgress); } return 0.5f; } if (s_pendingHit) { return Mathf.Lerp(0.5f, 1f, s_pendingPulledBack); } if (s_pendingProgress < 0f) { return 1f; } float num = Mathf.Clamp01(s_pendingProgress / (2f / 3f)); return 1f - (1f - num) * (1f - num); } private static bool CanDodge(Player player) { if (((Character)player).IsStaggering() || !((Character)player).IsOnGround()) { return false; } if (!((Character)player).HaveStamina(player.GetDodgeStaminaUse())) { Hud instance = Hud.instance; if (instance != null) { instance.StaminaBarEmptyFlash(); } return false; } return true; } private static void StartDodge(Player player, Vector3 dir) { //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_0035: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) ((Humanoid)player).ClearActionQueue(); player.m_queuedDodgeTimer = 0f; player.m_dodgeInvincible = true; ((Component)player).transform.rotation = Quaternion.LookRotation(dir); ((Character)player).m_body.rotation = ((Component)player).transform.rotation; ((Character)player).m_zanim.SetTrigger("dodge"); ((Character)player).AddNoise(5f); ((Character)player).UseStamina(player.GetDodgeStaminaUse()); player.m_dodgeEffects.Create(((Component)player).transform.position, Quaternion.identity, ((Component)player).transform, 1f, -1, ((Character)player).GetZDOID()); } private static void UpdateCheck() { if ((Object)(object)s_checkPlayer == (Object)null || Time.frameCount < s_checkFrame || Time.time < s_checkTime) { return; } Player val = s_checkPlayer; s_checkPlayer = null; if ((Object)(object)val != (Object)(object)Player.m_localPlayer) { return; } bool flag = false; Animator animator = ((Character)val).m_animator; for (int i = 0; i < animator.layerCount; i++) { if (LayerTag(animator, i) == Humanoid.s_animatorTagAttack) { flag = true; break; } } Debug("dodge cancel: " + (flag ? "still in attack animation, falling back to cross-fade" : "trigger worked")); if (flag) { CrossFadeOutOfAttack(animator); } } private static bool IsExcluded(string animation) { string[] array = Plugin.NonCancellableAttacks.Value.Split(','); for (int i = 0; i < array.Length; i++) { if (array[i].Trim().Equals(animation, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool HitDone(Attack attack) { if (s_triggered) { return s_tracked == attack; } return false; } private static void Track(Attack attack) { if (s_tracked != attack) { s_tracked = attack; s_triggered = false; } } private static bool KeepsCombo(bool hitDone) { if (hitDone) { return Plugin.KeepCombo.Value; } return false; } private static void TurnKeptCombo(Player player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_002e: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_005f: 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_006a: 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_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_004c: 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) //IL_0052: Unknown result type (might be due to invalid IL or missing references) Attack currentAttack = ((Humanoid)player).m_currentAttack; if (currentAttack == null || (int)currentAttack.m_attackType == 3 || currentAttack.m_currentAttackCainLevel == 0) { return; } Quaternion val; if (!player.AttackTowardsPlayerLookDir || (int)currentAttack.m_attackType == 2) { val = ((Character)player).GetLookYaw(); } else { Vector3 moveDir = ((Character)player).GetMoveDir(); if (((Vector3)(ref moveDir)).sqrMagnitude <= 0f) { return; } val = Quaternion.LookRotation(moveDir); } ((Component)player).transform.rotation = Quaternion.RotateTowards(((Component)player).transform.rotation, val, 90f); ((Character)player).m_body.rotation = ((Component)player).transform.rotation; } private static void ResetAttackTriggers(Animator animator, Attack attack) { animator.ResetTrigger(attack.m_attackAnimation); for (int i = 0; i < attack.m_attackChainLevels; i++) { animator.ResetTrigger(attack.m_attackAnimation + i); } } private static void CrossFadeOutOfAttack(Animator animator) { int num = Mathf.Min(animator.layerCount, s_restoreState.Length); for (int i = 0; i < num; i++) { if (LayerTag(animator, i) == Humanoid.s_animatorTagAttack) { if (s_restoreState[i] != 0) { animator.CrossFadeInFixedTime(s_restoreState[i], 0.15f, i); } else { Debug($"no state recorded to return to on animator layer {i}"); } } } } private static int LayerTag(Animator animator, int layer) { //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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AnimatorStateInfo val; if (!animator.IsInTransition(layer)) { val = animator.GetCurrentAnimatorStateInfo(layer); return ((AnimatorStateInfo)(ref val)).tagHash; } val = animator.GetNextAnimatorStateInfo(layer); return ((AnimatorStateInfo)(ref val)).tagHash; } private static void Debug(string message) { if (Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)message); } } } internal static class Controls { [HarmonyPatch(typeof(Player), "SetControls")] private static class SetControlsPatch { [HarmonyPriority(0)] private static void Prefix(Player __instance, bool block, bool blockHold, ref bool jump, ref bool dodge, out bool __state) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) __state = false; if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { if (Time.time - s_dodgePressedAt < 0.2f) { s_dodgePressedAt = float.NegativeInfinity; dodge = true; } bool flag = (__instance.m_toggleBlock ? (((Character)__instance).m_blocking != block) : blockHold); if (flag && !((Character)__instance).m_blocking) { Cancel.TryCancel(__instance, dodge: false); } if (jump && !dodge && flag && Plugin.BlockJump.Value != BlockJumpAction.Dodge && ((int)ZInput.InputLayout == 0 || !ZInput.IsGamepadActive())) { jump = false; __state = Plugin.BlockJump.Value == BlockJumpAction.Jump; } } } private static void Postfix(Player __instance, bool __state) { if (__state) { ((Character)__instance).Jump(false); } } } private const float PressLifetime = 0.2f; private static float s_dodgePressedAt = float.NegativeInfinity; internal static void PollDodgeKey() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!IsDown(Plugin.DodgeKey.Value)) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { PlayerController component = ((Component)localPlayer).GetComponent<PlayerController>(); if ((Object)(object)component != (Object)null && component.TakeInput(false) && !component.InInventoryEtc() && !Hud.InRadial()) { s_dodgePressedAt = Time.time; } } } private static bool IsDown(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_0039: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !UnityInput.Current.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (!UnityInput.Current.GetKey(modifier)) { return false; } } return true; } } internal static class HitProgress { private static readonly Dictionary<AnimationClip, float> s_hitFraction = new Dictionary<AnimationClip, float>(); internal static float Get(Animator animator) { if (!Find(animator, out var time, out var hit)) { return -1f; } return Mathf.Clamp01(time / hit); } internal static float Recovery(Animator animator) { if (!Find(animator, out var time, out var hit)) { return -1f; } if (!(hit < 1f)) { return 1f; } return Mathf.Clamp01((time - hit) / (1f - hit)); } private static bool Find(Animator animator, out float time, out float hit) { //IL_002b: 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_0030: 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_006e: Unknown result type (might be due to invalid IL or missing references) time = 0f; hit = -1f; for (int i = 0; i < animator.layerCount; i++) { bool flag = animator.IsInTransition(i); AnimatorStateInfo val = (flag ? animator.GetNextAnimatorStateInfo(i) : animator.GetCurrentAnimatorStateInfo(i)); if (((AnimatorStateInfo)(ref val)).tagHash != Humanoid.s_animatorTagAttack) { continue; } AnimatorClipInfo[] obj = (flag ? animator.GetNextAnimatorClipInfo(i) : animator.GetCurrentAnimatorClipInfo(i)); AnimationClip val2 = null; float num = -1f; AnimatorClipInfo[] array = obj; for (int j = 0; j < array.Length; j++) { AnimatorClipInfo val3 = array[j]; if (((AnimatorClipInfo)(ref val3)).weight > num) { val2 = ((AnimatorClipInfo)(ref val3)).clip; num = ((AnimatorClipInfo)(ref val3)).weight; } } time = ((AnimatorStateInfo)(ref val)).normalizedTime; hit = (((Object)(object)val2 != (Object)null) ? HitFraction(val2) : (-1f)); return hit > 0f; } return false; } private static float HitFraction(AnimationClip clip) { if (s_hitFraction.TryGetValue(clip, out var value)) { return value; } float num = float.MaxValue; AnimationEvent[] events = clip.events; foreach (AnimationEvent val in events) { if ((val.functionName == "Hit" || val.functionName == "OnAttackTrigger") && val.time < num) { num = val.time; } } value = ((num == float.MaxValue || clip.length <= 0f) ? (-1f) : (num / clip.length)); s_hitFraction[clip] = value; return value; } } public enum BlockJumpAction { Dodge, Jump, Nothing } [BepInPlugin("hvalch.Feint", "Feint", "0.3.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [SynchronizationMode(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "hvalch.Feint"; public const string Name = "Feint"; public const string Version = "0.3.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> CancelWithBlock; internal static ConfigEntry<bool> CancelWithDodge; internal static ConfigEntry<float> StaminaPenalty; internal static ConfigEntry<bool> ScaleStaminaPenalty; internal static ConfigEntry<string> NonCancellableAttacks; internal static ConfigEntry<float> CancelledHitDamage; internal static ConfigEntry<bool> ScaleCancelledHitDamage; internal static ConfigEntry<bool> DodgeAfterHit; internal static ConfigEntry<bool> DodgeAfterHitPenalty; internal static ConfigEntry<bool> KeepCombo; internal static ConfigEntry<float> ComboKeepSeconds; internal static ConfigEntry<KeyboardShortcut> DodgeKey; internal static ConfigEntry<BlockJumpAction> BlockJump; internal static ConfigEntry<bool> DebugLog; private readonly Harmony _harmony = new Harmony("hvalch.Feint"); private void Awake() { //IL_0189: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; CancelWithBlock = BindSynced("1 - Cancel", "CancelWithBlock", value: true, "Pressing block during an attack cancels it."); CancelWithDodge = BindSynced("1 - Cancel", "CancelWithDodge", value: true, "Dodging during an attack cancels it."); StaminaPenalty = BindSynced("1 - Cancel", "StaminaPenalty", 1f, "Extra stamina a cancel costs, as a multiple of the attack's own stamina cost (which is already spent when the swing starts). 0 = no extra cost.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f)); ScaleStaminaPenalty = BindSynced("1 - Cancel", "ScaleStaminaPenalty", value: true, "Scale the penalty by when you cancel: nothing at the very start of the swing, rising quickly to the full penalty for the latest cancel that still stops the hit, then down to half for a cancel right at the hit (when the hit lands anyway). Charged when the block/dodge starts. Off = always the full penalty. You still need the full penalty in stamina to cancel."); CancelledHitDamage = BindSynced("1 - Cancel", "CancelledHitDamage", 0.5f, "Damage multiplier for a cancelled swing that still reaches its hit before the block/dodge starts. 0 = no damage, 1 = full damage. With ScaleCancelledHitDamage, this is the lowest it goes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f)); ScaleCancelledHitDamage = BindSynced("1 - Cancel", "ScaleCancelledHitDamage", value: true, "Scale a cancelled swing's damage by how long you had been pulling back when it hit: full damage if it hit right after the cancel, down to CancelledHitDamage if it hit just before the block/dodge started. Off = always CancelledHitDamage."); DodgeAfterHit = BindSynced("1 - Cancel", "DodgeAfterHit", value: false, "Dodging after the swing has hit also cancels it, skipping the rest of the swing. The dodge starts right away (on landing, if in the air), also for NonCancellableAttacks. Needs CancelWithDodge. Off = vanilla (the dodge waits for the swing to end)."); DodgeAfterHitPenalty = BindSynced("1 - Cancel", "DodgeAfterHitPenalty", value: true, "Charge the StaminaPenalty for a dodge after the hit. With ScaleStaminaPenalty it falls from the at-hit amount (half) right after the hit to nothing at the end of the swing. Off = only the dodge's own stamina."); NonCancellableAttacks = BindSynced("1 - Cancel", "NonCancellableAttacks", "knife_secondary,dual_knives_secondary", "Comma-separated attack animation names that block and dodge can't cancel before the hit (e.g. the knife's jump attack). Only affects cancelling before the hit: DodgeAfterHit still works for them. DebugLog prints the name of each attack you cancel."); KeepCombo = BindSynced("3 - Combo", "KeepCombo", value: false, "Keep your combo going for ComboKeepSeconds between swings, also after a dodge or block. Cancelling a swing before it hits still resets the combo. The kept swing turns toward your aim by up to 90 degrees (vanilla only turns a combo's first swing). Off = vanilla (the combo resets 0.2 seconds after a swing ends, and any cancel resets it)."); ComboKeepSeconds = BindSynced("3 - Combo", "ComboKeepSeconds", 1f, "How long after a swing ends the next swing still continues the combo (KeepCombo).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 3f)); DodgeKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("2 - Controls", "DodgeKey", KeyboardShortcut.Empty, "Dedicated dodge key (dodges in the direction you're moving, forward if standing still). Same as the game's own 'AltDodge' binding. Local only, never synced."); BlockJump = ((BaseUnityPlugin)this).Config.Bind<BlockJumpAction>("2 - Controls", "BlockJump", BlockJumpAction.Dodge, "What jump does while blocking (keyboard): Dodge (vanilla), Jump, or Nothing. Local only, never synced."); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Controls", "DebugLog", false, "Log cancel attempts and why they were refused. Local only, never synced."); _harmony.PatchAll(); Log.LogInfo((object)"Feint 0.3.0 loaded"); } private void Update() { Controls.PollDodgeKey(); Cancel.Update(); } private void OnDestroy() { _harmony.UnpatchSelf(); } private ConfigEntry<T> BindSynced<T>(string section, string key, T value, string description, AcceptableValueBase range = null) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }