using System; using System.Collections; using System.Collections.Generic; using Beyond; using Invector.vCharacterController.AI; using Invector.vCharacterController.AI.FSMBehaviour; using UnityEngine; namespace Invector.vMelee { using vEventSystems; public class bMeleeAttackControl : StateMachineBehaviour { [Tooltip("normalizedTime of Active Damage")] public float startDamage = 0.05f; [Tooltip("normalizedTime of Disable Damage")] public float endDamage = 0.9f; public int damageMultiplier; public int recoilID; public int reactionID; public vAttackType meleeAttackType = vAttackType.Unarmed; [Tooltip("You can use a name as reference to trigger a custom HitDamageParticle")] public string damageType; [HideInInspector] [Header("This work with vMeleeManager to active vMeleeAttackObject from bodyPart and id")] public List bodyParts = new List { "RightLowerArm" }; public bool ignoreDefense; public bool activeRagdoll; [vHideInInspector("activeRagdoll"), Tooltip("Time to keep Ragdoll active")] public float senselessTime; [Tooltip("Check true in the last attack of your combo to reset the triggers")] public bool resetAttackTrigger; private bool isActive; public bool debug; private vIAttackListener mFighter; private bool isAttacking; //private bool slowMoActive = false; public bool useAttackTimeScale = false; public float resetTriggerBeforeTime = 0.5f; public float attackTimeScale = 0.2f; public float attackTimeScaleStart = -1f; public float attackTimeScaleEnd = -1f; public float maxTargetDistance = 3f; public float maxTurnTowardDistance = 6f; public float lowHealthTh = 10f; private bool m_hasScaledTime = false; public float degreeThreshold = 20; public bool rotatePlayerTowardsTarget; private bool m_isRotating; public float rotationSpeed = 30f; override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { mFighter = animator.GetComponent(); isAttacking = true; //slowMoActive = false; if (mFighter != null) mFighter.OnEnableAttack(); if (debug) Debug.Log("Enter " + damageType); if (attackTimeScaleStart < 0f) { attackTimeScaleStart = startDamage; } if (attackTimeScaleEnd < 0f) { attackTimeScaleEnd = endDamage; } } private void LerpRotation() { if (!rotatePlayerTowardsTarget) { return; } float minDist = maxTurnTowardDistance; var enemy = GetNearestEnemy(ref minDist); if (!IsEnemyInAngleRange(enemy)) { return; } Transform playerTransform = Player.Instance.transform; var toEnemy = enemy.transform.position - playerTransform.position; toEnemy.y = 0f; toEnemy.Normalize(); Quaternion targetRot = Quaternion.LookRotation(toEnemy); var rotation = playerTransform.rotation; rotation = Quaternion.RotateTowards(rotation, targetRot, Time.deltaTime * rotationSpeed); //rotation = Quaternion.Lerp(rotation, targetRot, Time.deltaTime * rotationSpeed); playerTransform.rotation = rotation; } private bool IsEnemyInAngleRange(vFSMBehaviourController ai) { if (ai == null) { return false; } Vector3 playerFwd = Player.Instance.transform.forward; Vector3 target = (ai.transform.forward - playerFwd).normalized; float dot = Vector3.Dot(playerFwd, target ); float angle = 180f - Mathf.Acos(dot) * Mathf.Rad2Deg; //Debug.Log("IsEnemyInAngleRange: angle: "+angle); if (angle > degreeThreshold) { return false; } return true; } public float NearTargertHealth() { float health = -1f; if (Player.Instance.LockOn.currentTarget != null) { var aic = Player.Instance.LockOn.currentTarget.GetComponent(); if (aic != null) { health = aic.currentHealth; return health; } } float minDist = Single.PositiveInfinity; var ctrl = GetNearestEnemy(ref minDist); if (minDist < maxTargetDistance) { health = ctrl.aiController.currentHealth; } return health; } private bool NearHealthLow() { float h = NearTargertHealth(); return h > 0f && h < lowHealthTh; } private vFSMBehaviourController GetNearestEnemy(ref float minDist) { var controllers = GameStateManager.Instance.GetActiveCombatcontrollers(); Vector3 playerPos = Player.Instance.transform.position; vFSMBehaviourController ctrl = null; foreach (var aic in controllers) { var dist2 = aic.transform.position - Player.Instance.transform.position; dist2.y = 0f; if (dist2.magnitude < minDist) { ctrl = aic; minDist = dist2.magnitude; } } return ctrl; } public bool TargetNear() { var targets = Player.Instance.LockOn.GetNearbyTargets(); if (targets.Count == 0) return false; else { var dist2 = targets[0].position - Player.Instance.transform.position; dist2.y = 0f; return dist2.magnitude < maxTargetDistance; } } private void UpdateSlowMo(float normalizedTime) { if (!m_hasScaledTime) { if (normalizedTime >= attackTimeScaleStart && normalizedTime <= attackTimeScaleEnd) { if (TargetNear() && (useAttackTimeScale || NearHealthLow())) { TimeController.Instance.SetTimeScaleForSec(attackTimeScale, attackTimeScaleEnd - normalizedTime, true); //Time.timeScale = attackTimeScale; m_hasScaledTime = true; } } } else if (normalizedTime > attackTimeScaleEnd) { m_hasScaledTime = false; } } override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (Player.Instance.ActiveWeaponTrail) { Player.Instance.ActiveWeaponTrail.m_colorMultiplier = Color.white +Color.red * damageMultiplier; } float nTime = stateInfo.normalizedTime % 1; LerpRotation(); if (nTime >= startDamage && nTime <= endDamage && !isActive) { if (debug) Debug.Log(animator.name + " attack " + damageType + " enable damage in " + System.Math.Round(stateInfo.normalizedTime % 1, 2)); isActive = true; ActiveDamage(animator, true); /* if (TargetNear() && (useAttackTimeScale || NearHealthLow())) { TimeController.Instance.SetTimeScaleForSec(attackTimeScale, endDamage - nTime); //Time.timeScale = attackTimeScale; m_hasScaledTime = true; } */ } else if (nTime > endDamage && isActive) { if (debug) Debug.Log(animator.name + " attack " + damageType + " disable damage in " + System.Math.Round(stateInfo.normalizedTime % 1, 2)); isActive = false; ActiveDamage(animator, false); if (m_hasScaledTime) { //Time.timeScale = 1f; m_hasScaledTime = false; } } if (nTime > endDamage && isAttacking) { isAttacking = false; if (mFighter != null) mFighter.OnDisableAttack(); } if (nTime > .1f && nTime < resetTriggerBeforeTime && isAttacking) { if (mFighter != null) mFighter.ResetAttackTriggers(); } //if (stateInfo.normalizedTime % 1 > allowRotationAt && isAttacking) //{ // isAttacking = false; // if (mFighter != null) // mFighter.OnDisableAttack(); //} UpdateSlowMo(nTime); } override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (debug) Debug.Log("Exit " + damageType); if (isActive) { isActive = false; ActiveDamage(animator, false); } if (isAttacking) { isAttacking = false; if (mFighter != null) mFighter.OnDisableAttack(); } if (mFighter != null && resetAttackTrigger) mFighter.ResetAttackTriggers(); if (debug) Debug.Log(animator.name + " attack " + damageType + " stateExit"); } void ActiveDamage(Animator animator, bool value) { var meleeManager = animator.GetComponent(); if (meleeManager) meleeManager.SetActiveAttack(bodyParts, meleeAttackType, value, damageMultiplier, recoilID, reactionID, ignoreDefense, activeRagdoll, senselessTime, damageType); } } }